关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » .NET »使用 C# 设计 Fluent Interface

    使用 C# 设计 Fluent Interface

    2015-04-06 00:00:00 出处:邹琼俊
    分享

    我们经常使用的一些框架例如: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#设计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#设计Fluent Interface

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

    使用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。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    Destoon 模板存放规则及语法参考

    Destoon系统常量与变量

    Destoon系统目录文件结构说明

    Destoon 系统安装指南

    Destoon会员公司主页模板风格添加方法

    Destoon 二次开发入门

    Microsoft 将于 2026 年 10 月终止对 Windows 11 SE 的支持

    Windows 11 存储感知如何设置?了解Windows 11 存储感知开启的好处

    Windows 11 24H2 更新灾难:系统升级了,SSD固态盘不见了...

    小米路由器买哪款?Miwifi热门路由器型号对比分析

    IT头条

    Synology 对 Office 套件进行重大 AI 更新,增强私有云的生产力和安全性

    01:43

    StorONE 的高效平台将 Storage Guardian 数据中心占用空间减少 80%

    11:03

    年赚千亿的印度能源巨头Nayara 云服务瘫痪,被微软卡了一下脖子

    12:54

    国产6nm GPU新突破!砺算科技官宣:自研TrueGPU架构7月26日发布

    01:57

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    技术热点

    如何删除自带的不常用应用为windows 7减负

    MySQL中多表删除方法

    改进的二值图像像素标记算法及程序实现

    windows 7 32位系统下手动修改磁盘属性例如M盘修改为F盘

    windows 7中怎么样在家庭组互传文件

    Linux应用集成MySQL数据库访问技巧

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。