Fluent Interface是一种经过连续的方法调用以完成特定逻辑处理的API实现方式,在代码中引入Fluent Interface不只可以提升开发效率,并且在提升代码可读性上也有很大的帮助。从C# 3.0开始,随着扩展方法的引入,Fluent Interface也更多地被开发人员熟悉和使用。例如,当咱们但愿从一个整数列表中找出全部的偶数,并将这些偶数经过降序排列的方式添加到另外一个列表中时,可使用下面的代码:html
1缓存 2框架 3ide 4函数 |
|
这段代码不只看起来很是清晰,并且在编写的时候也更符合人脑的思惟方式,经过这些连续的方法调用,咱们首先从列表i中寻找全部的偶数,而后对这些偶数进行排序并将排序后的值逐个添加到result列表中。日志
在实际应用中,Fluent Interface不只仅是使用在相似上面的查询逻辑上,而它更多地是被应用开发框架的配置功能所使用,好比在Entity Framework Code First中可使用Fluent API对实体(Entity)和模型(Model)进行配置,此外还有流行的ORM框架NHibernate以及企业服务总线框架NServiceBus等等,都提供了相似的Fluent API,以简化框架的配置过程。这些API都是Fluent Interface的具体实现。因为Fluent Interface的方法链中各方法的名称都具备很强的描述性,并且具备单一职责的特色,因此Fluent Interface也能够当作是完成某一领域特定任务的“领域特定语言(Domain Specific Language)”,好比在上面的例子中,Fluent Interface被用于查询领域,而在Entity Framework、NHiberante和NServiceBus等框架中,它又被用于框架的配置领域。
接下来,让咱们首先看一下Fluent Interface的简单实现方式,并简要地讨论一下这种实现方式的优缺点,再来了解一下一种使用装饰器(Decorator)模式和扩展接口的实现方式。
Fluent Interface的一种简单实现就是在类型的每一个方法中对传入参数进行处理,而后返回该类型自己的实例,所以,当该类型的某个方法被调用后,进而还能够连续地直接调用其它的方法而无需在调用时指定该类型的实例。现假设咱们须要实现某个服务接口IService,在这个接口中,要用到一个提供缓存功能的接口ICache以及一个提供日志记录的接口ILogger,为了让IService的实例可以以Fluent Interface的方式指定本身所须要的ICache接口和ILogger接口的实例,咱们能够这样定义IService接口:
1 2 3 4 5 6 7 |
|
因而,对IService实例的配置就变得很是简单,好比:
1 2 |
|
这是最简单的Fluent Interface的实现方式,对于一些简单的应用场景,使用这种简单快捷的方式的确是个不错的选择,但在体验着这种便捷的同时,咱们或许还须要进行更进一步的思考:
鉴于以上三点分析,当须要在应用程序或开发框架中更为合理地引入Fluent Interface时,上述简单的实现方式就没法知足全部需求了。为此,我采用装饰器模式,并结合C#的扩展方法特性来实现Fluent Interface,这种方式不只可以解决上面的三种问题,并且面向对象的设计会使Fluent Interface的扩展变得更加简单。
仍然以上文中的IService接口为例,经过分析咱们能够获得两个启示:首先,对于IService的实例究竟应该是采用哪一种缓存机制以及哪一种日志记录机制,这就是一种对IService的实例进行配置的过程;其次,这种配置过程就至关于在每一个配置阶段逐渐地向已有的配置信息上添加新的信息,好比最开始建立一个空的配置信息,在第一阶段肯定了所选用的缓存机制时,就会在这个空的配置信息基础上添加与缓存相关的配置信息,而在第二阶段肯定了所选用的日志记录机制时,又会在前一阶段得到的配置信息基础上再添加与日志记录相关的配置信息,这个过程正好是装饰器模式的一种应用场景。最后一步就很是简单了,程序只须要根据最终获得的配置信息初始化IService接口的实例便可。为了简化实现过程,我选择Microsoft Patterns & Practices Unity Application Block的IoC容器来实现这个配置信息的管理机制。选用Unity IoC容器的好处是,对接口及其实现类型的注册并无前后顺序的要求,IoC容器会自动分析类型之间的依赖关系并对类型进行注册。事实上在不少应用程序开发框架中,也是用这种方式在框架的配置部分实现Fluent Interface的。
首先咱们引入“配置器”的概念,配置器的做用就是对IService实例初始化过程当中的某个方面(例如缓存或者日志)进行配置,它会向调用者返回一个Unity IoC容器的实例,以便调用方可以在该配置的基础上进行其它方面的配置操做(为了简化起见,下文中所描述的“配置”仅表示选择某种特定类型的实现,而不包含其它额外的配置内容)。咱们可使用以下接口对配置器进行定义:
1 2 3 4 |
|
为了实现的方便,咱们还将引入一个抽象类,该抽象类实现了IConfigurator接口,并将其中的Configure方法标识为抽象方法。因而,对于任何一种配置器而言,它只须要继承于该抽象类,而且重载Configure方法便可实现配置逻辑。该抽象类的定义以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
接下来就是针对不一样的配置环节实现各自的配置器了。咱们以缓存机制的配置为例,简要介绍一下“缓存配置器”的实现方式。
先定义一个名为ICacheConfigurator的接口,该接口实现了IConfigurator的接口,但它是一个空接口,并不包含任何属性、事件或方法的接口定义。引入这个接口的目的就是要在接下来的扩展方法定义中可以实现面向该接口的方法扩展,因而上文中讨论的第二个问题就能引刃而解,这将在接下来的“扩展方法的引入”部分进行讨论。事实上在不少成熟的应用程序和框架中也有相似的设计,好比将接口用做泛型约束类型等。所以,ICacheConfigurator的实现代码很是简单:
1 2 3 |
|
而做为“缓存配置器”而言,它只须要继承于Configurator类并实现ICacheConfigurator接口就能够了,代码以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
从上面的代码中能够看到,TCache约束于ICache接口类型,而在Configure方法中,首先调用配置上下文(也就是配置器自己所包含的上一层配置器实例)的Configure方法,同时得到已配置的Unity IoC容器实例container,以后在container上继续调用RegisterType方法,将给定的缓存机制实现类型注册到container中,最后将container返回给调用者。
整个配置器部分的实现,能够用下面的类图进行总结:
前面已经提到过,扩展方法能够将职责无关的方法定义从类型中移出,并在一个静态类中进行集中实现。在目前的这个例子中,扩展方法还可以帮助咱们将类型继承的层次结构“扁平化”,使得Fluent Interface中各方法的衔接逻辑变得更加清晰。仍然以缓存配置部分为例,假设咱们但愿在得到了服务的配置以后,可以接着对缓存机制进行配置,在完成了缓存机制的配置后,才能开始对日志记录机制进行配置,那么咱们就能够定义扩展方法以下:
1 2 3 4 5 6 7 8 |
|
上面的WithDictionaryCache方法表示须要在Service的配置上采用基于字典的缓存机制,而WithConsoleLogger则表示在缓存配置的基础上,还须要选用控制台做为日志记录机制。
从上面的代码中咱们还能了解到,扩展方法还可以很直观地定义各类配置之间的前后顺序,更改起来也很是方便。例如,若是缓存机制和日志记录机制的配置没有一个先后关系的话,那么咱们能够将IServiceConfigurator做为WithConsoleLogger的第一个参数类型,而无需去修改代码中的其它任何部分。
接下来要作的,就是设计一个工厂类,使其可以根据咱们的配置信息建立一个新的IService实例。
工厂类的实现就很是简单了,一样使用扩展方法,对IConfigurator类型进行扩展,在得到了Unity IoC容器的实例以后,只须要调用Resolve方法直接返回IService类型的实现类型就能够了。Resolve方法的使用,直接解决了上文中提到的第三个问题。工厂类的代码以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
建立一个测试项目以便对咱们所作的工做进行测试,好比下面的测试方法将会对IService的实现所采用的缓存机制类型和日志记录机制类型进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
如今咱们已经可使用Fluent Interface对IService实例的初始化过程进行配置了。Fluent Interface的引入,更像是在使用一种天然语言对配置过程进行表述:Service factory, to config (the) service with Appfabric Cache (mechanism) (and) with Database Logger (mechanism)。
本文首先介绍了Fluent Interface的相关知识,并给出了一种简单的实现方式。经过对简单实现方式的讨论,引出了可能存在的设计问题,进而选择了一种更为合理的实现方式,即经过使用装饰器模式和C#的扩展方法特性来实现Fluent Interface。这种全新的实现方式不只可以解决所讨论的设计问题,并且这种面向对象的设计方式还为Fluent Interface的实现带来了必定的可扩展性。文章最后对这种实现方式进行了简单测试,同时也展现了Fluent Interface在实际中的应用。
本文所讨论的案例源代码能够在http://sdrv.ms/SxRKqG 站点下载。
【apworks.org站点本文连接地址:http://apworks.org/?p=334】