Płynne interfejsy i dziedziczenie w C#

Pokażę problem na przykładzie. Istnieje klasa bazowa z płynnym interfejsem:

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

I klasa dziecięca:

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

Problem polega na tym, że gdy wywołujesz customer.WithAccountNumber("000").WithFirstName("John").WithLastName("Smith") nie możesz dodać .WithId(123) na końcu, ponieważ zwracanym typem metody WithLastName() jest FluentPerson (nie FluentCustomer).

Jak zwykle rozwiązywano ten problem?

Author: bniwredyc, 2010-02-17

6 answers

Możesz użyć leków generycznych, aby to osiągnąć.

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

A teraz:

var customer = new FluentCustomer()
  .WithAccountNumber("123")
  .WithFirstName("Abc")
  .WithLastName("Def")
  .ToString();
 38
Author: Yann Trevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-17 13:51:22

Spróbuj użyć metod rozszerzania.

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

Po można użyć jak

new FluentCustomer().WithId(22).WithFirstName("dfd").WithId(32);
 36
Author: Steck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-17 07:39:55

Logicznie trzeba skonfigurować rzeczy od najbardziej konkretnego (klienta) do najmniej konkretnego (osoby) lub w inny sposób nawet trudno jest to odczytać pomimo płynnego interfejsu. Przestrzegając tej zasady w większości przypadków nie będziesz musiał mieć kłopotów. Jeśli jednak z jakiegoś powodu nadal musisz go miksować, możesz użyć pośrednich wyrażeń podkreślających, takich jak

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

customer.WIthLastName("Bob").AsCustomer().WithId(10);
 4
Author: Dzmitry Huba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-17 06:47:05

Rozwiązanie, w którym potrzebujesz płynnego interfejsu, dziedziczenia, a także niektórych leków generycznych...

Tak czy inaczej, jak powiedziałem wcześniej: jest to jedyna opcja, jeśli chcesz użyć dziedziczenia i dostępu również chronionych członków...

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);
    }
}
 4
Author: baHI,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-15 05:32:04
 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);
       }
   }

I wywołaj go za pomocą

  var ss = new FluentCustomer().WithAccountNumber("111").WithFirstName("ram").WithLastName("v").WithId("444").ToString();
 3
Author: RameshVel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-17 07:08:21

Czy płynny interfejs jest naprawdę najlepszym wywołaniem tutaj, czy może inicjalizator byłby lepszy?

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

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

W przeciwieństwie do interfejsu fluent, działa to dobrze bez odziedziczonych metod oddających klasę bazową i zakłócających łańcuch. Kiedy dziedziczysz nieruchomość, rozmówca naprawdę nie powinien dbać o to, czy {[1] } został po raz pierwszy zaimplementowany osobiście, Klient lub obiekt.

Uważam, że jest to również bardziej czytelne, czy to w jednej linii, czy w wielu, i nie musisz zadawać sobie kłopotu z zapewnienie płynnych funkcji samodzielnego dekorowania, które odpowiadają każdej nieruchomości.

 3
Author: richardtallent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-17 07:10:30