C#编程实例:使用C#设计Fluent Interface
安安 2017-09-28 来源 :网络 阅读 679 评论 0

摘要:本篇C#编程实例教程将为大家讲解C#编程的知识点,看完这篇文章会让你对C#编程的知识点有更加清晰的理解和运用。

本篇C#编程实例教程将为大家讲解C#编程的知识点,看完这篇文章会让你对C#编程的知识点有更加清晰的理解和运用。

我们经常使用的一些框架例如:EF,Automaper,NHibernate等都提供了非常优秀的Fluent Interface, 这样的API充分利用了VS的智能提示,而且写出来的代码非常整洁。我们如何在代码中也写出这种Fluent的代码呢,我这里介绍3总比较常用的模式,在这些模式上稍加改动或者修饰就可以变成实际项目中可以使用的API,当然如果没有设计API的需求,对我们理解其他框架的代码也是非常有帮助。

一、最简单且最实用的设计

这是最常见且最简单的设计,每个方法内部都返回return this; 这样整个类的所有方法都可以一连串的写完。代码也非常简单:

使用起来也非常简单:

   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

        }

调用:

[Test]

        public void All_shows_can_be_invoked_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没法满足这样的需求,使用者可以随心所欲改变调用顺序。

 C#编程实例:使用C#设计Fluent Interface

如上图所示,vs将所有的方法都提示了出来。

我们知道,作为一个优秀的API,要尽量避免让使用者犯错,比如要设计private 字段,readonly 字段等都是防止使用者去修改内部数据从而导致出现意外的结果。

二、设计具有调用顺序的Fluent API

在之前的例子中,API设计者期望使用者首先调用StartShow()方法来初始化一些数据,然后进行表演,最后使用者方可调用EndShow(),实现的思路是将不同种类的功能抽象到不同的接口中或者抽象类中,方法内部不再使用return this,取而代之的是return INext;

根据这个思路,我们将StartShow(),和EndShow()方法抽象到一个类中,而将马戏团的表演抽象到一个接口中:

  public abstract class Performer

    {

        public abstract ICircusPlayer CircusPlayer { get;  }

        public abstract ICircusPlayer StartShow();

        public abstract void EndShow();

    }

    public interface ICircusPlayer

    {

        IList PlayedItem { get;  }

        ICircusPlayer MonkeysPlay();

        ICircusPlayer ElephantsPlay();

        Performer TogetherPlay();

    }

有了这样的分类,我们重新设计API,将StartShow()和EndShow()设计在CircusPerfomer中,将马戏团的表演项目设计在CircusPlayer中:

  public class CircusPerformer:Performer

    {

        private  ICircusPlayer _circusPlayer;

 

        override public ICircusPlayer CircusPlayer { get { return _circusPlayer; } }

 

        public override ICircusPlayer StartShow()

        {

            //make a speech and start to show

            _circusPlayer=new CircusPlayer(this);

            return _circusPlayer;

        }

 

        public override void EndShow()

        {

            //finish the show

        }

    }

  public class CircusPlayer:ICircusPlayer

    {

        private readonly Performer _performer;

        public  IList PlayedItem { get; private set; }

 

        public CircusPlayer(Performer performer)

        {

            _performer = performer;

            PlayedItem=new List();

        }

 

        public ICircusPlayer MonkeysPlay()

        {

            PlayedItem.Add("MonkeyPlay");

            //monkeys do some show

            return this;

        }

 

        public ICircusPlayer ElephantsPlay()

        {

            PlayedItem.Add("ElephantPlay");

            //elephants do some show

            return this;

        }

 

        public Performer TogetherPlay()

        {

            PlayedItem.Add("TogetherPlay");

            //all of the animals do some show

            return _performer;

        }

    }

这样的API可以满足我们的要求,在马戏团circusPerformer实例上只能调用StartShow()和EndShow()

 C#编程实例:使用C#设计Fluent Interface

调用完StartShow()后方可调用各种表演方法。

 C#编程实例:使用C#设计Fluent Interface

当然由于我们的API很简单,所以这个设计还算说得过去,如果业务很复杂,需要考虑众多的情形或者顺序我们可以进一步完善,实现的基本思想是利用装饰者模式和扩展方法,由于园子里的dax.net在很早前就发表了相关博客在C#中使用装饰器模式和扩展方法实现Fluent Interface,所以大家可以去看这篇文章的实现方案,该设计应该可以说是终极模式,实现过程也较为复杂。

三、泛型类的Fluent设计

泛型类中有个不算问题的问题,那就是泛型参数是无法省略的,当你在使用var list=new List<string>()这样的类型时,必须指定准确的类型string。相比而言泛型方法中的类型时可以省略的,编译器可以根据参数推断出参数类型,例如

 var circusPerfomer = new CircusPerfomerWithGenericMethod();

            circusPerfomer.Show<Dog>(new Dog());

            circusPerfomer.Show(new Dog());

如果想省略泛型类中的类型有木有办法?答案是有,一种还算优雅的方式是引入一个非泛型的静态类,静态类中实现一个静态的泛型方法,方法最终返回一个泛型类型。这句话很绕口,我们不妨来看个一个画图板实例吧。

定义一个Drawing<TShape>类,此类可以绘出TShape类型的图案

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来

  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的方法我们可以这样设计:

首先这样的设计要借助于一个静态类:

   public static class Drawer

    {

        public static Drawing<TShape> For<TShape>(TShape shape) where TShape:IShape

        {

            return new Drawing<TShape>();

        }

    }

然后利用这个静态类画一个Dog

  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配置的时候经常这样写:

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的关键代码:

  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,我们还需要定义一个静态非泛型类,

public static class Path

    {

        public static Path<TModel> For<TModel>(TModel model)

        {

            var path = new Path<TModel>(model);

            return path;

        }

    }

定义Validator,这个类可以读取到配置的信息,

  public Validator<TValue> MapPath(params PropertyItem<TValue>[] properties)

        {

            foreach (var propertyItem in properties)

            {

                _items.Add(propertyItem);

            }

            return this;

        }

最后调用

  [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语言更加清晰并且不失优雅, Path.For(A).Property(x=>x.Name).OnCondition(B),这句话可以翻译为,对A的属性Name设置条件为B。


本文由职坐标整理并发布,希望对同学们学习C#编程的知识有所帮助。了解更多详情请关注职坐标C#频道!


本文由 @安安 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程