65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (14投票s)

2010 年 6 月 27 日

CPOL

4分钟阅读

viewsIcon

52367

downloadIcon

347

设计模式系列 - 第一部分

目录

引言

本系列文章将通过 C# 的实际示例,探讨设计模式在日常开发实践中的实际用途和应用。第一篇文章(希望还有更多)将涵盖装饰器模式。本系列文章也发布在我的 博客 C# | B# | Stay# [^] 上,正如其名,它赞美一切与 # 相关的事物!

背景与概念

让我们从一些理论开始。根据 GoF(Gang of Four),装饰器设计模式的意图是:

“动态地为对象附加额外的责任。装饰器为扩展功能提供了比子类化更灵活的替代方案。”

这引自他们具有里程碑意义的著作《设计模式:可复用面向对象软件的基础》[^]。在我看来,在软件开发中最令人愉悦的事情之一是,当我们能够成功地实现一个模式,不是通过预先的意图,而是通过手头代码的自然和有机发展。我们发现了变化并试图封装它,然后瞧,我们就无意中使用了某个模式,从而实现了它的意图和目的。

我认为面向对象软件开发者的“圣杯”(也许是一个略显自大的说法)是识别任何给定需求中的共同点和变化点,然后找到一种方法来封装这些变化,使得未来的(几乎不可避免的需求变更)相对容易实现。

在短暂的“技术唠叨”之后,我们可以回到我们的主题。装饰器设计模式。概念上,该模式看起来是这样的:

decorator_pattern.jpg

装饰器模式允许我创建一系列对象(装饰器链),即负责新功能的装饰器,并以原始对象结尾。调用链如下:

CallChain.jpg

这不应与链表混淆。它应该被视为一组可选的装饰对象。

经典示例

该模式的一个经典示例是流 I/O 库。对于任何特定的流,只有一个输入,但可以对输入流执行零个或多个操作。

例如,如下面的示例所示,我可以从内存流读取,过滤流,然后使用自定义流读取器读取它。所有这些组件(后两个是自定义对象)都实现了 stream 抽象 类,并在其构造函数中接受一个 stream 对象。调用链如下:

Stream filteredMemoryStream = new StreamReader(new StreamFilter(new MemoryStream()));

查询装饰器

我个人使用装饰器模式来解决将 LINQ 查询分解成更细粒度和可重用部分的问题。客户端可以调用多个相似但只有细微差别的 LINQ 查询。直接结果是每个查询中都重复了 LINQ 代码。另一个副作用是调用客户端和查询组合之间的紧密耦合。

我在这里使用的虚构示例假设我们正在查询一个 Employees 存储库,并且有大量查询可能包含相同的表达式和子句。例如,我们需要获取所有经理。另一个要求是我们获取所有经理并且年龄在一定范围内的。然后我们可能需要获取这些经理的前 20%。所有这些额外的查询都转化为可重用的装饰器,如下所示:

QueryDecorator.png

作为这种设计的直接结果,查询组合可以在一个工厂内部进行链式组合,该工厂根据简单的条件逻辑或可能的配置文件来实例化所需的查询。

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));
        }
    }

为了进一步阐明手头的示例,我添加了 QueryComponentQueryWrapper 的简单实现,以及每个的具体实现。

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 日 - 添加源代码示例
© . All rights reserved.