使用装饰器模式组合 LINQ 查询






4.71/5 (14投票s)
设计模式系列 -
目录
引言
本系列文章将通过 C# 的实际示例,探讨设计模式在日常开发实践中的实际用途和应用。第一篇文章(希望还有更多)将涵盖装饰器模式。本系列文章也发布在我的 博客 C# | B# | Stay# [^] 上,正如其名,它赞美一切与 # 相关的事物!
背景与概念
让我们从一些理论开始。根据 GoF(Gang of Four),装饰器设计模式的意图是:
“动态地为对象附加额外的责任。装饰器为扩展功能提供了比子类化更灵活的替代方案。”
这引自他们具有里程碑意义的著作《设计模式:可复用面向对象软件的基础》[^]。在我看来,在软件开发中最令人愉悦的事情之一是,当我们能够成功地实现一个模式,不是通过预先的意图,而是通过手头代码的自然和有机发展。我们发现了变化并试图封装它,然后瞧,我们就无意中使用了某个模式,从而实现了它的意图和目的。
我认为面向对象软件开发者的“圣杯”(也许是一个略显自大的说法)是识别任何给定需求中的共同点和变化点,然后找到一种方法来封装这些变化,使得未来的(几乎不可避免的需求变更)相对容易实现。
在短暂的“技术唠叨”之后,我们可以回到我们的主题。装饰器设计模式。概念上,该模式看起来是这样的:
装饰器模式允许我创建一系列对象(装饰器链),即负责新功能的装饰器,并以原始对象结尾。调用链如下:
这不应与链表混淆。它应该被视为一组可选的装饰对象。
经典示例
该模式的一个经典示例是流 I/O 库。对于任何特定的流,只有一个输入,但可以对输入流执行零个或多个操作。
例如,如下面的示例所示,我可以从内存流读取,过滤流,然后使用自定义流读取器读取它。所有这些组件(后两个是自定义对象)都实现了 stream 抽象
类,并在其构造函数中接受一个 stream
对象。调用链如下:
Stream filteredMemoryStream = new StreamReader(new StreamFilter(new MemoryStream()));
查询装饰器
我个人使用装饰器模式来解决将 LINQ 查询分解成更细粒度和可重用部分的问题。客户端可以调用多个相似但只有细微差别的 LINQ 查询。直接结果是每个查询中都重复了 LINQ 代码。另一个副作用是调用客户端和查询组合之间的紧密耦合。
我在这里使用的虚构示例假设我们正在查询一个 Employees
存储库,并且有大量查询可能包含相同的表达式和子句。例如,我们需要获取所有经理。另一个要求是我们获取所有经理并且年龄在一定范围内的。然后我们可能需要获取这些经理的前 20%。所有这些额外的查询都转化为可重用的装饰器,如下所示:
作为这种设计的直接结果,查询组合可以在一个工厂内部进行链式组合,该工厂根据简单的条件逻辑或可能的配置文件来实例化所需的查询。
public class EmployeeQueryFactory
{
public static QueryComponent<Employee> GetQuery()
{
/*
* We decouple the calling client from the query component instantiation
* by using this factory method where we encapsulate all conditional
* logic that determines which query component to instantiate
*/
//Get all employees who are contractees
return new ContracteesQuery(new EmployeeQuery());
//Or get all managers older than 50
return new AgeLimitQuery(new ManagersQuery(new EmployeeQuery()), 50);
//Or get top %20 of all contractees older than 30
return new Top20PercentQuery(new AgeLimitQuery
(new ContracteesQuery(new EmployeeQuery()), 30));
}
}
为了进一步阐明手头的示例,我添加了 QueryComponent
和 QueryWrapper
的简单实现,以及每个的具体实现。
public abstract class QueryComponent<T>
{
public abstract IQueryable<T> Query();
}
public class EmployeeQuery : QueryComponent<Employee>
{
public override IQueryable<Employee> Query()
{
return new EmployeesRepository().GetAllEmployess().AsQueryable();
}
}
public abstract class QueryWrapper<T> : QueryComponent<T>
{
protected readonly QueryComponent<T> QueryComponent;
protected QueryWrapper(QueryComponent<T> queryComponent)
{
QueryComponent = queryComponent;
}
protected IQueryable<T> CallTrailer()
{
return null != QueryComponent ? QueryComponent.Query() : null;
}
}
public class ContracteesQuery : QueryWrapper<Employee>
{
public ContracteesQuery(QueryComponent<Employee> queryComponent)
: base(queryComponent)
{
}
public override IQueryable<Employee> Query()
{
return CallTrailer().Where(p => p.IsContractee);
}
}
最后,像往常一样,我留下这句引言,来自 Christopher Alexander [^]:
“我们在寻找某种和谐,介于两个无形之物之间:一个我们尚未设计出的形式,和一个我们无法充分描述的上下文。”
好好编码,保持 #!
下载次数
这里是一个示例 VS 2008 SP1 项目,其中包含查询包装器示例。它是一个控制台应用程序,它从 EmployeeQueryFactory
执行一个示例查询。
历史
- 2010 年 6 月 27 日 - 提交初稿
- 2010 年 6 月 28 日 - 添加源代码示例