C#中的Fluent接口模式探讨:解决继承难题

Fluent接口模式——简介

Fluent Interface Pattern是面向对象(OO)语言的设计指南,它建议公开API,即类public方法应该为了提高可读性,尝试“出现”为领域特定语言(DSL)。建议创建DSL的主要工具是“方法链”。我们希望有这样的接口(API):

Employee empl = new Employee();empl.SetFirstName("John").SetLastName("Smith").SetAge(30).Print();


方法链在C#中与许多OO语言一样,通过返回对象本身(即“ this”对象)作为方法结果来实现。这使得下一个方法可以链接到前一个方法的返回值。在抽象层面上,我们实际上是在将域上下文返回给方法链接中的后续方法。方法链接以返回void上下文的方法终止。

Fluent Interface Pattern只关心接口应该是什么样子,它并没有规定经典意义上的模式如何实现它。只要你有方法链形式的接口,“看起来像”自然语言,因此被认为是“用户友好的”,它认为你创建了自己的“领域特定语言(DSL)”形式,并且是对此感到高兴。

经典方法——Setters——示例代码

这是一个使用标准设置器的经典代码示例。这种编程在OO和C#文献中被广泛推荐。

Employee empl = new Employee();empl.SetFirstName("John").SetLastName("Smith").SetAge(30).Print();


这是执行结果:


FirstName:John; LastName:Smith; Age:30

Fluent接口——方法链——示例代码

这是带有方法链接的代码示例。这与上面示例中的类基本相同,遵循Fluent Interface Pattern的建议。此处应用的主要技巧是返回“this”对象,以便可以链接方法。

public class Employee{    private string FirstName = null;    private string LastName = null;    private int Age = 0;     public Employee SetFirstName(string fName)    {        FirstName = fName;        return this;    }     public Employee SetLastName(string lName)    {        LastName = lName;        return this;    }     public Employee SetAge(int age)    {        Age = age;        return this;    }     public void Print()    {        string tmp = String.Format("FirstName:{0}; LastName:{1}; Age:{2}",            FirstName, LastName, Age);        Console.WriteLine(tmp);    }} class Client{    static void Main(string[] args)    {        Employee empl = new Employee();        empl.SetFirstName("John").SetLastName("Smith").SetAge(30).Print();         Console.ReadLine();    }}


这是执行结果:


FirstName:John; LastName:Smith; Age:30

Fluent接口——使用扩展的方法链——示例代码

当然,我们可以使用C#中的扩展方法来达到与上述相同的结果。请注意,我们需要更改Employee属性的访问级别为public,以便扩展方法可以使用它们。

public class Employee{    public string FirstName = null;    public string LastName = null;    public int Age = 0;} public static class EmployeeExtensions{    public static Employee SetFirstName(this Employee emp, string fName)    {        emp.FirstName = fName;        return emp;    }     public static Employee SetLastName(this Employee emp, string lName)    {        emp.LastName = lName;        return emp;    }     public static Employee SetAge(this Employee emp, int age)    {        emp.Age = age;        return emp;    }     public static void Print(this Employee emp)    {        string tmp = String.Format("FirstName:{0}; LastName:{1}; Age:{2}",           emp.FirstName, emp.LastName, emp.Age);        Console.WriteLine(tmp);    }} class Client{    static void Main(string[] args)    {        Employee empl = new Employee();        empl.SetFirstName("John").SetLastName("Smith").SetAge(30).Print();         Console.ReadLine();    }}


这是执行结果:


FirstName:John; LastName:Smith; Age:30

分层Fluent接口

如前所述,Fluent Interface模式留给程序员和他的判断来决定他的“特定领域语言(DSL)”的特定版本将是什么样子。程序员可以自由选择方法名称和方法链接的组织,这在他看来是最“用户友好的”和最相似的自然语言。

一个典型的用例是创建分层流利接口。在这种情况下,程序员决定将属性分组到分层组中,并以这种方式将其公开给库/类用户。例如,程序员可能决定将Employee数据分为两组:

  1. PersonalData; 和
  2. EmploymentData

这将导致这样的接口:

Employee empl = new Employee(); empl.Fluent    .PersonalData        .FirstName("John").LastName("Smith").Age(30)    .EmploymentData        .Company("CNN").Position("Host").Salary(50000);


我们将在这里提供如何创建这样一个接口的示例项目。不一定是程序员应该遵循的模式来实现分层Fluent 接口。如果愿意,程序员可以自由选择自己的实现技术。但是,这提供了一个很好且可重用的示例代码。我们不会详细介绍这个项目的设计/实现细节,因为大部分都是不言自明的。

这是类图:

这是分层Fluent 接口的代码:

public class Employee{    //PersonalData    public string FirstName = null;    public string LastName = null;    public int Age = 0;     //EmploymentData    public string Company = null;    public string Position = null;    public int Salary = 0;     public FluentEmployee Fluent    {        get { return new FluentEmployee(this); }    }     public override string ToString()    {        string tmp = String.Format("FirstName:{0}; LastName:{1}; Age:{2}  \nCompany:{3}; Position:{4}; Salary:{5}",            this.FirstName, this.LastName, this.Age, this.Company,             this.Position, this.Salary);        return tmp;    }} public class FluentEmployee{    protected Employee _employee = null;     public FluentEmployee()    {        _employee = new Employee();    }     public FluentEmployee(Employee emp)    {        _employee = emp;    }     public FluentEmployeePersonalData PersonalData    {        get { return new FluentEmployeePersonalData(_employee); }    }     public FluentEmployeeEmploymentData EmploymentData    {        get { return new FluentEmployeeEmploymentData(_employee); }    }} public class FluentEmployeeEmploymentData : FluentEmployee{    public FluentEmployeeEmploymentData(Employee emp) : base(emp)    {    }     public FluentEmployeeEmploymentData Company(string comp)    {        _employee.Company = comp;        return this;    }     public FluentEmployeeEmploymentData Position(string pos)    {        _employee.Position = pos;        return this;    }     public FluentEmployeeEmploymentData Salary(int sal)    {        _employee.Salary = sal;        return this;    }} public class FluentEmployeePersonalData : FluentEmployee{    public FluentEmployeePersonalData(Employee emp) : base(emp)    {    }     public FluentEmployeePersonalData FirstName(string fName)    {        _employee.FirstName = fName;        return this;    }     public FluentEmployeePersonalData LastName(string lName)    {        _employee.LastName = lName;        return this;    }     public FluentEmployeePersonalData Age(int age)    {        _employee.Age = age;        return this;    }} class Client{    static void Main(string[] args)    {        Employee empl = new Employee();         empl.Fluent            .PersonalData                .FirstName("John").LastName("Smith").Age(30)            .EmploymentData                .Company("CNN").Position("Host").Salary(50000);         Console.WriteLine(empl.ToString());        Console.ReadLine();    }}


下面是执行结果:

FirstName:John; LastName:Smith; Age:30Company:CNN; Position:Host; Salary:50000


继承Fluent接口类

当我们要继承实现Fluent接口的类时,就会出现问题。让我们假设我们有类Employee和Manager继承自它。让它像这个类图一样:

这是代码:


public class Employee{    protected string FirstName = null;    protected string LastName = null;    protected int Age = 0;     public Employee SetFirstName(string fName)    {        FirstName = fName;        return this;    }     public Employee SetLastName(string lName)    {        LastName = lName;        return this;    }     public Employee SetAge(int age)    {        Age = age;        return this;    }     public override string ToString()    {        string tmp = String.Format("FirstName:{0}; LastName:{1}; Age:{2}",            FirstName, LastName, Age);        return tmp;    }} public class Manager : Employee{    protected int Bonus = 0;     public Manager SetBonus(int bonus)    {        Bonus = bonus;        return this;    }     public override string ToString()    {        string tmp = String.Format("FirstName:{0}; LastName:{1}; Age:{2}; Bonus:{3}",            FirstName, LastName, Age, Bonus);        return tmp;    }}


我们想要的是这样的接口:

Manager mgr = new Manager(); mgr.SetFirstName("John").SetLastName("Smith")    .SetAge(30).SetBonus(1000);     //will not compile


但这不会编译。原因是我们现在返回了两种类型的“this”对象,类型Employee和类型Manager。一旦我们取回基类对象“this” as Employee,类的方法Manager就不再可用了。

因此,方法Employee.SetAge()将返回Employee类型的对象,并且无法从类Manager访问方法链。

那么,有没有办法欺骗方法Employee.SetAge()返回子类Manager类型的对象?我们需要它来实现我们的Fluent Interface,即Method-Chaining?

答案是肯定的,有可能,但技术上比较复杂。它涉及“递归泛型”的使用。我将在这里展示一个工作代码示例。

我不会详细解释解决方案的工作原理。如果您能阅读C#泛型代码,您将能够理解这种相当高级的设计。

该解决方案需要解决的关键问题是如何将返回类类型从派生类传递到基类。关键技巧是始终返回原始(派生)类的Fluent Interface对象。这是使用泛型和类型参数SELF解决的。从继承层次结构中的大多数派生类(如CEO、经理、员工)中遵循SELF,您将了解此代码的工作原理。Where子句(如“where SELF:EmployeeFluent<SELF>”)只是为了确保该类被用作继承层次结构的一部分。

这是继承Fluent接口类的解决方案的类图。

这是继承Fluent Interface类的解决方案的代码。


public class EmployeeFluent<SELF>        where SELF : EmployeeFluent<SELF>{    protected string FirstName = null;    protected string LastName = null;    protected int Age = 0;     public SELF SetFirstName(string fName)    {        FirstName = fName;        return (SELF)this;    }     public SELF SetLastName(string lName)    {        LastName = lName;        return (SELF)this;    }     public SELF SetAge(int age)    {        Age = age;        return (SELF)this;    }     public override string ToString()    {        string tmp = String.Format("FirstName:{0}; LastName:{1}; Age:{2}",            FirstName, LastName, Age);        return tmp;    }} public class Employee : EmployeeFluent<Employee> { }; public class ManagerFluent<SELF> : EmployeeFluent<SELF>        where SELF : ManagerFluent<SELF>{    protected int Bonus = 0;     public SELF SetBonus(int bonus)    {        Bonus = bonus;        return (SELF)this;    }     public override string ToString()    {        string tmp = String.Format("FirstName:{0}; LastName:{1}; Age:{2}; Bonus:{3}",            FirstName, LastName, Age, Bonus);        return tmp;    }} public class Manager : ManagerFluent<Manager> { }; public class CEOFluent<SELF> : ManagerFluent<SELF>        where SELF : CEOFluent<SELF>{    protected int CompanyShares = 0;     public SELF SetCompanyShares(int shares)    {        CompanyShares = shares;        return (SELF)this;    }     public override string ToString()    {        string tmp = String.Format("FirstName:{0}; LastName:{1}; Age:{2}; Bonus:{3};  CompanyShares:{4}",            FirstName, LastName, Age, Bonus, CompanyShares);        return tmp;    }} public class CEO : CEOFluent<CEO> { }; class Client{    static void Main(string[] args)    {        CEO ceo1 = new CEO();         ceo1.SetFirstName("John").SetLastName("Smith")            .SetAge(30).SetBonus(1000).SetCompanyShares(5000);         Manager mgr = new Manager();         mgr.SetFirstName("Cedomir").SetLastName("Jokic")            .SetAge(40).SetBonus(2000);         Employee emp = new Employee();         emp.SetFirstName("Novak").SetLastName("Djokovic")            .SetAge(20);         Console.WriteLine(ceo1.ToString());        Console.WriteLine(mgr.ToString());        Console.WriteLine(emp.ToString());        Console.ReadLine();    }}


这是继承Fluent接口类的此解决方案的示例执行。








免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删

QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空