Fluent interfaces and inheritance in C#

a 夏天 提交于 2019-11-26 12:04:16

问题


I\'ll show a problem by example. There is a base class with fluent interface:

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);
    }
}

and a child class:

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);
    }
}

The problem is that when you call customer.WithAccountNumber(\"000\").WithFirstName(\"John\").WithLastName(\"Smith\") you can\'t add .WithId(123) in the end because return type of the WithLastName() method is FluentPerson (not FluentCustomer).

How this problem usually solved?


回答1:


You can use generics to achieve that.

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;
    }
}

And now:

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



回答2:


Try to use some Extension methods.

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);
    }
}

after you can use like

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



回答3:


Logically you need to configure stuff from most specific (customer) to least specific (person) or otherwise it is even hard to read it despite the fluent interface. Following this rule in most cases you won't need get into trouble. If however for any reason you still need to mix it you can use intermediate emphasizing statements like

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

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



回答4:


A solution where you need fluent interface, inheritance and also some generics...

Anyhow as I stated before: this is the only option if you want to use inheritance and access also protected members...

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);
       }
   }

And invoke it using

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



回答6:


Is a fluent interface really the best call here, or would an initializer be better?

 var p = new Person{
      LastName = "Smith",
      FirstName = "John"
      };

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

Unlike a fluent interface, this works fine without inherited methods giving back the base class and messing up the chain. When you inherit a property, the caller really shouldn't care whether FirstName was first implemented in Person or Customer or Object.

I find this more readable as well, whether on one line or multiple, and you don't have to go through the trouble of providing fluent self-decorating functions that correspond with each property.




回答7:


I know this is now an old question, but I wanted to share my thoughts about this with you.

What about separating fluency, which is a kind of mechanism, and your classes, when you can ? This would leave your classes pure.

What about something like this ?

The classes

    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}");
        }
    }

A class that adds some fluent mechanism

    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);
        }
    }

An extension method to switch to fluent mode

    public static class CustomerExtensions 
    {

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

The same example as in question


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



来源:https://stackoverflow.com/questions/2278781/fluent-interfaces-and-inheritance-in-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!