C#中设计Fluent API
我们经常使用的一些框架例如:EF,Automaper,NHibernate等都提供了非常优秀的Fluent API, 这样的API充分利用了VS的智能提示,而且写出来的代码非常整洁。我们如何在代码中也写出这种Fluent的代码呢,我这里介绍3总比较常用的模式,在这些模式上稍加改动或者修饰就可以变成实际项目中可以使用的API,当然如果没有设计API的需求,对我们理解其他框架的代码也是非常有帮助。
一、最简单且最实用的设计
这是最常见且最简单的设计,每个方法内部都返回return this; 这样整个类的所有方法都可以一连串的写完。代码也非常简单:
使用起来也非常简单:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
public class CircusPerformer { public List< string > PlayedItem { get ; private set ; } public CircusPerformer() { PlayedItem= new List< string >(); } public CircusPerformer StartShow() { //make a speech and start to show return this ; } public CircusPerformer MonkeysPlay() { //monkeys do some show PlayedItem.Add( "MonkeyPlay" ); return this ; } public CircusPerformer ElephantsPlay() { //elephants do some show PlayedItem.Add( "ElephantPlay" ); return this ; } public CircusPerformer TogetherPlay() { //all of the animals do some show PlayedItem.Add( "TogetherPlay" ); return this ; } public void EndShow() { //finish the show } |
调用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
[Test] public void All_shows_can_invoke_by_fluent_way() { //Arrange var circusPerformer = new CircusPerformer(); //Act circusPerformer .MonkeysPlay() .ElephantsPlay() .StartShow() .TogetherPlay() .EndShow(); //Assert circusPerformer.PlayedItem.Count.Should().Be(3); circusPerformer.PlayedItem.Contains( "MonkeysPlay" ); circusPerformer.PlayedItem.Contains( "ElephantsPlay" ); circusPerformer.PlayedItem.Contains( "TogetherPlay" ); } |
但是这样的API有个瑕疵,马戏团circusPerformer在表演时是有顺序的,首先要调用StartShow(),其次再进行各种表演,表演结束后要调用EndShow()结束表演,但是显然这样的API没法满足这样的需求,使用者可以随心所欲改变调用顺序。
我们知道,作为一个优秀的API,要尽量避免让使用者犯错,比如要设计private 字段,readonly 字段等都是防止使用者去修改内部数据从而导致出现意外的结果。
二、设计具有调用顺序的Fluent API
在之前的例子中,API设计者期望使用者首先调用StartShow()方法来初始化一些数据,然后进行表演,最后使用者方可调用EndShow(),实现的思路是将不同种类的功能抽象到不同的接口中或者抽象类中,方法内部不再使用return this,取而代之的是return INext;
根据这个思路,我们将StartShow(),和EndShow()方法抽象到一个类中,而将马戏团的表演抽象到一个接口中:
1
2
3
4
5
6
|
public abstract class Performer { public abstract IList< string > PlayedItem { get ; protected set ; } public abstract ICircusPlayer StartShow(); public abstract void EndShow(); } |
1
2
3
4
5
6
|
public interface ICircusPlayer { ICircusPlayer MonkeysPlay(); ICircusPlayer ElephantsPlay(); ICircusPlayer TogetherPlay(); } |
有了这样的分类,我们重新设计API:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public class CircusPerfomer:Performer,ICircusPlayer { public override sealed IList< string > PlayedItem { get ; protected set ; } public CircusPerfomer() { PlayedItem = new List< string >(); } public override ICircusPlayer StartShow() { //make a speech and start to show return this ; } public ICircusPlayer MonkeysPlay() { //monkeys do some show PlayedItem.Add( "MonkeyPlay" ); return this ; } public ICircusPlayer ElephantsPlay() { //elephants do some show PlayedItem.Add( "ElephantPlay" ); return this ; } public ICircusPlayer TogetherPlay() { //all of the animals do some show PlayedItem.Add( "TogetherPlay" ); return this ; } public override void EndShow() { //finish the show } } |
这样的API可以满足我们的要求,在马戏团circusPerformer实例上只能调用StartShow()和EndShow(),调用完StartShow()后方可调用各种表演方法。
当然由于我们的API很简单,所以这个设计还算说得过去,如果业务很复杂,需要考虑众多的情形或者顺序我们可以进一步完善,实现的基本思想是利用装饰者模式和扩展方法,由于园子里的dax.net在很早前就发表了相关博客在C#中使用装饰器模式和扩展方法实现Fluent Interface,所以大家可以去看这篇文章的实现方案,该设计应该可以说是终极模式,实现过程也较为复杂。
三、泛型类的Fluent设计
泛型类中有个不算问题的问题,那就是泛型参数是无法省略的,当你在使用var list=new List<string>()这样的类型时,必须指定准确的类型string。相比而言泛型方法中的类型时可以省略的,编译器可以根据参数推断出参数类型,例如
1
2
3
|
var circusPerfomer = new CircusPerfomerWithGenericMethod(); circusPerfomer.Show<Dog>( new Dog()); circusPerfomer.Show( new Dog()); |
如果想省略泛型类中的类型有木有办法?答案是有,一种还算优雅的方式是引入一个非泛型的静态类,静态类中实现一个静态的泛型方法,方法最终返回一个泛型类型。这句话很绕口,我们不妨来看个一个画图板实例吧。
定义一个Drawing<TShape>类,此类可以绘出TShape类型的图案
1
2
3
4
5
6
7
8
9
10
|
public class Drawing<TShape> where TShape :IShape { public TShape Shape { get ; private set ; } public TShape Draw(TShape shape) { //drawing this shape Shape = shape; return shape; } } |
定义一个Canvas类,此类可以画出Pig,根据传入的基本形状,调用对应的Drawing<TShape>来组合出一个Pig来
1
2
3
4
5
6
7
8
9
|
public void DrawPig(Circle head, Rectangle mouth) { _history.Clear(); //use generic class, complier can not infer the correct type according to parameters Register( new Drawing<Circle>().Draw(head), new Drawing<Rectangle>().Draw(mouth) ); } |
这段代码本身是非常好懂的,而且这段代码也很clean。如果我们在这里想使用一下之前提到过的技巧,实现一个省略泛型类型且比较Fluent的方法我们可以这样设计:
首先这样的设计要借助于一个静态类:
1
2
3
4
5
6
7
|
public static class Drawer { public static Drawing<TShape> For<TShape>(TShape shape) where TShape:IShape { return new Drawing<TShape>(); } } |
然后利用这个静态类画一个Dog
1
2
3
4
5
6
7
8
9
|
public void DrawDog(Circle head, Rectangle mouth) { _history.Clear(); //fluent implements Register( Drawer.For(head).Draw(head), Drawer.For(mouth).Draw(mouth) ); } |
可以看到这里已经变成了一种Fluent的写法,写法同样比较clean。写到这里我脑海中浮现出来了一句”费这劲干嘛”,这也是很多人看到这里要想说的,我只能说你完全可以把这当成是一种奇技淫巧,如果哪天遇到使用的框架有这种API,你能明白这是怎么回事就行。
四、案例
写到这里我其实还想举一个例子来说说这种技巧在有些情况下是很常用的,大家在写EF配置,Automaper配置的时候经常这样写:
1
2
3
4
5
6
7
8
|
xx.MapPath( Path.For(_student).Property(x => x.Name), Path.For(_student).Property(x => x.Email), Path.For(_customer).Property(x => x.Name), Path.For(_customer).Property(x => x.Email), Path.For(_manager).Property(x => x.Name), Path.For(_manager).Property(x => x.Email) ) |
这样的写法就是前面的技巧改变而来,我们现在设计一个Validator,假如说这个Validator需要批量对Model的字段进行验证,我们也需要定义一个配置文件,配置某某Model的某某字段应该怎么样,利用这个配置我们可以验证出哪些数据不符合这个配置。
配置文件类Path的关键代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class Path<TModel> { private TModel _model; public Path(TModel model) { _model = model; } public PropertyItem<TValue> Property<TValue>(Expression<Func<TModel, TValue>> propertyExpression) { var item = new PropertyItem<TValue>(propertyExpression.PropertyName(), propertyExpression.PropertyValue(_model),_model); return item; } } |
为了实现fluent,我们还需要定义一个静态非泛型类,
1
2
3
4
5
6
7
8
|
public static class Path { public static Path<TModel> For<TModel>(TModel model) { var path = new Path<TModel>(model); return path; } } |
定义Validator,这个类可以读取到配置的信息,
1
2
3
4
5
6
7
8
|
public Validator<TValue> MapPath( params PropertyItem<TValue>[] properties) { foreach ( var propertyItem in properties) { _items.Add(propertyItem); } return this ; } |
最后调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
[Test] public void Should_validate_model_values() { //Arrange var validator = new Validator< string >(); validator.MapPath( Path.For(_student).Property(x => x.Name), Path.For(_student).Property(x => x.Email), Path.For(_customer).Property(x => x.Name), Path.For(_customer).Property(x => x.Email), Path.For(_manager).Property(x => x.Name), Path.For(_manager).Property(x => x.Email) ) .OnCondition((model)=>! string .IsNullOrEmpty(model.ToString())); //Act validator.Validate(); //Assert var result = validator.Result(); result.Count.Should().Be(3); result.Any(x => x.ModelType == typeof (Student) && x.Name == "Email" ).Should().Be( true ); result.Any(x => x.ModelType == typeof (Customer) && x.Name == "Name" ).Should().Be( true ); result.Any(x => x.ModelType == typeof (Manager) && x.Name == "Email" ).Should().Be( true ); } |
结束语:有了这些Fluent API设计方式,大家在设计自己的API时可以设计出更优雅更符合语义的API,本文提供下载本文章所使用的源码,vs2013创建,测试项目使用了Nunit和FluentAssertions,如需转载请注明出处。
C#中设计Fluent API的更多相关文章
- 1.【使用EF Code-First方式和Fluent API来探讨EF中的关系】
原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/relationship-in-entity-framework-using-code-firs ...
- EF里的默认映射以及如何使用Data Annotations和Fluent API配置数据库的映射
I.EF里的默认映射 上篇文章演示的通过定义实体类就可以自动生成数据库,并且EF自动设置了数据库的主键.外键以及表名和字段的类型等,这就是EF里的默认映射.具体分为: 数据库映射:Code First ...
- EF:Fluent API 把一对多映射为一对一
假设有两张表:A表和B表.A表与B表在数据库中的关系是一对多,但我们需要在EF中映射为一对一. 首先在A实体类和B实体类中互相为对方增加一个实体类的属性: public A { public B B ...
- 8.2 使用Fluent API进行实体映射【Code-First系列】
现在,我们来学习怎么使用Fluent API来配置实体. 一.配置默认的数据表Schema Student实体 using System; using System.Collections.Gener ...
- 8.3 使用Fluent API进行属性映射【Code-First系列】
现在,我打算学习,怎么用Fluent API来配置领域类中的属性. using System; using System.Collections.Generic; using System.Linq; ...
- EF Fluent API上
什么是Fluent API? 官方答案:EF 中内嵌的约定将 POCO 类映射到表.但是,有时您无法或不想遵守这些约定,需要将实体映射到约定指示外的其他对象,所以Fluent API和注解都是一种方 ...
- Entity Framework Code First 中使用 Fluent API 笔记。
在做MVC+EF CodeFirst 的Demo时,碰到的问题, 在组册用户时,要让用户输入确认密码,但是数据库中又不需要保存这个字段,解决方案很多了,这里我列出通过EF Code First的解决方 ...
- ORM系列之二:EF(4) 约定、注释、Fluent API
目录 1.前言 2.约定 2.1 主键约定 2.2 关系约定 2.3 复杂类型约定 3.数据注释 3.1 主键 3.2 必需 3.3 MaxLength和MinLength 3.4 NotMapped ...
- Fluent API 配置
EF里实体关系配置的方法,有两种: Data Annotation方式配置 也可以 Fluent API 方式配置 Fluent API 配置的方法 EF里的实体关系 Fluent API 配置分为H ...
随机推荐
- Intent用法
Intent是android系统中的最佳男主角,Intent翻译成中文的意思是"意图",说白了就是"我想要...",也就是说眼下运行中的Activity想要请其 ...
- ECharts SSH+JQueryAjax+Json+JSP在数据库中的数据来填充ECharts在
1导入包.设定SSH框架. 进口JQuery的JS包.<script src="JS/jquery-1.7.1.js"></script> 导入EChart ...
- SqlServer 添加列并赋值
有个需求,需要给某张表添加一列并且赋值,分解需求,一共分两部走: 添加列 赋值 两个功能都不难,很快实现. --add column alter table Med_Summary_Template ...
- SQL Server 2008性能故障排查(三)——I/O
原文:SQL Server 2008性能故障排查(三)--I/O 接着上一章:CPU瓶颈 I/O瓶颈(I/O Bottlenecks): SQLServer的性能严重依赖I/O子系统.除非你的数据库完 ...
- Ehcache BigMemory: 摆脱GC困扰(转)
问题 使用java开源项目经常需要调优jvm,以优化gc.对于gc,如果对象都是短时对象,那么jvm相对容易优化,假如碰上像solr使用自带java cache的项目,那么gc严重受限于cache,因 ...
- 简单工厂模式—>工厂模式
一.功能 根据前一篇博客:策略模式+单例模式+简单工厂模式:推送服务,想试用一下工厂模式:将之前的简单工厂模式格式化为工厂模式. 二.实现 修改前:简单工厂 public static class P ...
- 弹出层 div dialog
写你自己的弹出框 风格,如下面 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcWluZ2xpYW5sdWFu/font/5a6L5L2T/fontsize ...
- 浅析Java中的final关键字(转)
谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法.下 ...
- NGUI Example5 演示示例评论– lights and Refraction
NGUI Example5 – lights and Refraction NUI这个系统是在是牛.比unity3D里面自带的gui要好用得多.还能够为GUI加入法线贴图! 哈哈. ...
- django 简易博客开发 1 安装、创建、配置、admin使用(转)
Django 自称是“最适合开发有限期的完美WEB框架”.本文参考<Django web开发指南>,快速搭建一个blog 出来,在中间涉及诸多知识点,这里不会详细说明,如果你是第一次接触D ...