【问题标题】:Fluent interfaces and inheritance in C#C#中的流利接口和继承
【发布时间】:2010-02-17 06:36:56
【问题描述】:

我将举例说明一个问题。有一个具有流畅接口的基类:

class FluentPerson
{
    private string _FirstName = String.Empty;
    private string _LastName = String.Empty;

    public FluentPerson WithFirstName(string firstName)
    {
        _FirstName = firstName;
        return this;
    }

    public FluentPerson WithLastName(string lastName)
    {
        _LastName = lastName;
        return this;
    }

    public override string ToString()
    {
        return String.Format("First name: {0} last name: {1}", _FirstName, _LastName);
    }
}

还有一个子类:

class FluentCustomer : FluentPerson
{
    private long _Id;

    private string _AccountNumber = String.Empty;

    public FluentCustomer WithAccountNumber(string accountNumber)
    {
        _AccountNumber = accountNumber;
        return this;
    }
    public FluentCustomer WithId(long id)
    {
        _Id = id;
        return this;
    }

    public override string ToString()
    {
        return base.ToString() + String.Format(" account number: {0} id: {1}", _AccountNumber, _Id);
    }
}

问题是当你调用customer.WithAccountNumber("000").WithFirstName("John").WithLastName("Smith")时你不能在最后添加.WithId(123),因为WithLastName()方法的返回类型是FluentPerson(不是FluentCustomer)。

这个问题通常是如何解决的?

【问题讨论】:

  • 有趣的线程!问题和给出的答案,从禁用继承 (Ramesh)、创建将 Person 类型转换为 Customer (Dzimtry) 的扩展方法、Yann 对“基”类的泛型的有趣使用、RichardTallent 将其分解为两个单独的类具有重复字段和 Gorpik 的 cmets :都增加了我的信念,即我应该远离使用流畅的接口进行编程。对我来说,方法链的“美”并不能证明这里提出的“瑜伽代码扭曲”是合理的。但我准备像往常一样“吃我的话”:)
  • @BillW:你是对的。实际上我问了这个问题,因为我已经有一个具有流畅接口的类,我需要实现另一个类,它使用了第一类的很多功能。还有一个要求是不要破坏旧代码。我认为流畅的界面不是一个通用的东西,你可以没有它,但在某些情况下它们真的很方便(不是必需的)。顺便说一句,我也对答案中建议的方法范围感到惊讶。
  • 我刚刚探索了下面的解决方案,我认为它归结为这些权衡:1)泛型允许具有继承(并使用受保护的成员),但只允许 2 级继承。如果您的类已经使用泛型,它可能会变得非常混乱。 2)扩展允许有多个继承级别(并且非常干净),但不允许使用受保护的成员。你可能不得不揭露一些事情。 3)组合(而不是继承)提供了更大的灵活性,但可能变得过于冗长(每个派生类都必须委托它继承的所有方法)。

标签: c# inheritance fluent-interface


【解决方案1】:

尝试使用一些扩展方法。

static class FluentManager
{
    public static T WithFirstName<T>(this T person, string firstName) where T : FluentPerson
    {
        person.FirstName = firstName;
        return person;
    }

    public static T WithId<T>(this T customer, long id) where T : FluentCustomer
    {
        customer.ID = id;
        return customer;
    }
}

class FluentPerson
{
    public string FirstName { private get; set; }
    public string LastName { private get; set; }

    public override string ToString()
    {
        return string.Format("First name: {0} last name: {1}", FirstName, LastName);
    }
}

class FluentCustomer : FluentPerson
{
    public long ID { private get; set; }
    public long AccountNumber { private get; set; }

    public override string ToString()
    {
        return base.ToString() + string.Format(" account number: {0} id: {1}", AccountNumber, ID);
    }
}

以后可以用like

new FluentCustomer().WithId(22).WithFirstName("dfd").WithId(32);

【讨论】:

  • 我比其他提议的想法更喜欢这种方法,因为它在可扩展性方面提供了最大的灵活性。当然,缺点是如果 OP 的目标 .NET 版本不支持它!
  • 赞成,如果需要流畅的方法,我也更喜欢这个。当然,缺点是扩展方法只能访问对象的公共成员,所以流畅的扩展方法可能需要在对象上调用一个传统的方法(所以可以做私有的工作)然后返回对象。跨度>
  • 如果您有两个具有相同属性的独立类层次结构,则不能使用流畅的扩展方法。
  • 扩展不是一个选项,因为您只能访问公共属性,也不能解决继承问题/对于每个继承的新类,您必须创建新的扩展方法 - 这几乎是与包装每个成员类相同。
【解决方案2】:

您可以使用泛型来实现。

public class FluentPerson<T>
    where T : FluentPerson<T>
{
    public T WithFirstName(string firstName)
    {
        // ...
        return (T)this;
    }

    public T WithLastName(string lastName)
    {
        // ...
        return (T)this;
    }
}

public class FluentCustomer : FluentPerson<FluentCustomer>
{
    public FluentCustomer WithAccountNumber(string accountNumber)
    {
        // ...
        return this;
    }
}

现在:

var customer = new FluentCustomer()
  .WithAccountNumber("123")
  .WithFirstName("Abc")
  .WithLastName("Def")
  .ToString();

【讨论】:

  • 看起来不错,但你不能从FluentCustomer进一步继承。
  • 你建议如何创建一个 FluentPerson 的实例? =)
  • @Gorpik;好点子。事实上,我认为应该可以进一步继承,但语法可能很难看(使用嵌套泛型)。无论如何,它适用于简单的场景;例如,当您需要在一个通用的抽象构建器之上构建几个专门的构建器时。
  • @Steck。应该可能需要定义一个从类的泛型版本继承的默认非泛型类型。类似的东西:public class FluentPerson : FluentPerson { }。现在您可以使用:var person = new FluentPerson();.
  • @Yann Trevin:您确定是否可以进一步继承?我想不通。如果是这样,您可以发布对此答案的更新吗? (我保证会投赞成票,但我已经做到了。)
【解决方案3】:

从逻辑上讲,您需要配置从最具体(客户)到最不具体(人)的内容,否则即使界面流畅,也很难阅读。在大多数情况下,遵循此规则您不会遇到麻烦。但是,如果出于任何原因您仍然需要混合它,您可以使用中间强调语句,如

static class Customers
{
   public static Customer AsCustomer(this Person person)
   {
       return (Customer)person;
   }
}

customer.WIthLastName("Bob").AsCustomer().WithId(10);

【讨论】:

    【解决方案4】:

    您需要流畅的接口、继承和一些泛型的解决方案...

    无论如何,正如我之前所说:如果您想使用继承并访问受保护的成员,这是唯一的选择...

    public class GridEx<TC, T> where TC : GridEx<TC, T>
    {
        public TC Build(T type)
        {
            return (TC) this;
        }
    }
    
    public class GridExEx : GridEx<GridExEx, int>
    {
    
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            new GridExEx().Build(1);
        }
    }
    

    【讨论】:

      【解决方案5】:
       public class FluentPerson
       {
          private string _FirstName = String.Empty;
          private string _LastName = String.Empty;
      
          public FluentPerson WithFirstName(string firstName)
          {
              _FirstName = firstName;
              return this;
          }
      
          public FluentPerson WithLastName(string lastName)
          {
              _LastName = lastName;
              return this;
          }
      
          public override string ToString()
          {
              return String.Format("First name: {0} last name: {1}", _FirstName, _LastName);
          }
      }
      
      
         public class FluentCustomer 
         {
             private string _AccountNumber = String.Empty;
             private string _id = String.Empty;
             FluentPerson objPers=new FluentPerson();
      
      
      
             public FluentCustomer WithAccountNumber(string accountNumber)
             {
                 _AccountNumber = accountNumber;
                 return this;
             }
      
             public FluentCustomer WithId(string id)
             {
                 _id = id;
                 return this;
             }
      
             public FluentCustomer WithFirstName(string firstName)
             {
                 objPers.WithFirstName(firstName);
                 return this;
             }
      
             public FluentCustomer WithLastName(string lastName)
             {
                 objPers.WithLastName(lastName);
                 return this;
             }
      
      
             public override string ToString()
             {
                 return objPers.ToString() + String.Format(" account number: {0}",  _AccountNumber);
             }
         }
      

      并调用它使用

        var ss = new FluentCustomer().WithAccountNumber("111").WithFirstName("ram").WithLastName("v").WithId("444").ToString();
      

      【讨论】:

      • 有趣:您现在已经消除了从 FluentPerson 类继承的 FluentCustomer 类。
      • @BillW,使用接口实现继承总是比类继承好(虽然我没有在我的回答中尝试过,但这样做很简单)。
      • 我对您的解决方案很着迷,因为在我看来,不是从“客户”“isA”“人”(继承)的角度来看,而是从“客户”“有一个”人(太糟糕了,我不够聪明,不知道那是不是“组合”:)。 Namaste 或 Vannakum,如果更合适的话。
      • @BillW,谢谢队友.. 是的,它是一种组合形式,实际上是它的“Vanakkam”:) ..
      【解决方案6】:

      流利的接口真的是最好的调用,还是初始化器更好?

       var p = new Person{
            LastName = "Smith",
            FirstName = "John"
            };
      
       var c = new Customer{
            LastName = "Smith",
            FirstName = "John",
            AccountNumber = "000",
            ID = "123"
            };
      

      与流畅的接口不同,这可以正常工作,而无需继承方法返回基类并弄乱链。当你继承一个属性时,调用者真的不应该关心FirstName是首先在Person、Customer还是Object中实现的。

      我发现这也更具可读性,无论是一行还是多行,并且您不必费心提供与每个属性相对应的流畅的自修饰函数。

      【讨论】:

      • 我认为OP只是用一个简单的例子来说明问题。
      • “课程用马”我想。添加到上面 Gorpik 的评论中,使用流利的接口将允许您将设置器与其他行为元素(如果它们已实现)结合起来,而初始化器将限制您仅设置固定的值集合,并且需要重载或其他一些构造可选元素。 :-)
      • Initializer 元素是 all 可选的,并且 any 可以初始化公共可写属性。不需要超载。但是对于设置更复杂的行为而不是简单的原始设置器的组合),你是对的——流利的函数很方便。
      • 我的立场是正确的!这就是我浏览代码示例而不是实际阅读它的结果! ;-) 顺便说一句,回复:可读性,我发现流畅的接口在“明智地”使用和实现时往往更具可读性,但当实现者试图将太多东西联系在一起时,可读性会降低。如果您试图对它们过于聪明,那么流畅的界面是一种将 API 实现意大利化的好方法。我的 OTOH 经验是,如果稍微考虑一下,他们也可以简化设计。
      【解决方案7】:

      我知道这是一个老问题,但我想与您分享我对此的看法。

      如果可以的话,如何将流利度(一种机制)和您的类分开?这将使您的课程保持纯净。

      这样的事情怎么样?

          public class Person
          {
              public string FirstName { get; set; }
              public string LastName {get; set;}
      
              public override string ToString()
              {
                  return $"First name: {FirstName} last name: {LastName}";
              }
          }
      
          public class Customer : Person
          {
              public string AccountNumber { get; set; }
              public long Id { get; set; }
      
              public override string ToString()
              {
                  return base.ToString() + $" account number: {AccountNumber} id: {Id}");
              }
          }
      

      添加一些流畅机制的类

          public class FluentCustomer 
          {
              private Customer Customer { get; }
      
              public FluentCustomer() : this(new Customer())
              {
              }
      
              private FluentCustomer(Customer customer)
              {
                  Customer = customer;
              }
      
              public FluentCustomer WithAccountNumber(string accountNumber)
              {
                  Customer.AccountNumber = accountNumber;
                  return this;
              }
      
              public FluentCustomer WithId(long id)
              {
                  Customer.Id = id;
                  return this;
              }
      
              public FluentCustomer WithFirstName(string firstName)
              {
                  Customer.FirstName = firstName;
                  return this;
              }
      
              public FluentCustomer WithLastName(string lastName)
              {
                  Customer.LastName = lastName;
                  return this;
              }
      
              public static implicit operator Customer(FluentCustomer fc)
              {
                  return fc.Customer;
              }
      
              public static implicit operator FluentCustomer(Customer customer)
              {
                  return new FluentCustomer(customer);
              }
          }
      

      切换到流利模式的扩展方法

          public static class CustomerExtensions 
          {
      
              public static FluentCustomer Fluent(this Customer customer)
              {
                  return customer;
              }
          }
      

      与问题相同的示例

      
              Customer customer = new Customer().Fluent()
                                  .WithAccountNumber("000")
                                  .WithFirstName("John")
                                  .WithLastName("Smith")
                                  .WithId(123);
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-31
        • 1970-01-01
        • 2021-08-10
        • 1970-01-01
        • 1970-01-01
        • 2011-04-21
        相关资源
        最近更新 更多