C#中的Fluent接口模式——存在继承问题

目录

Fluent接口模式——简介

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

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

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

分层Fluent接口

继承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——示例代码

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

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:30
Company:CNN; Position:Host; Salary:50000

继承Fluent接口类

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

这是代码:

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接口类的此解决方案的示例执行。

结论

Fluent Interface Pattern已经获得了广泛的欢迎,并且将继续存在。这里只提一下广泛使用的LINQ

https://www.codeproject.com/Articles/5326456/Fluent-Interface-Pattern-in-Csharp-With-Inheritanc

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值