Interfaces fluidas y herencia en C#


Voy a mostrar un problema por ejemplo. Hay una clase base con interfaz fluida:

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

Y una clase hija:

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

El problema es que cuando llamas a customer.WithAccountNumber("000").WithFirstName("John").WithLastName("Smith") no puedes agregar .WithId(123) al final porque el tipo de retorno del método WithLastName() es FluentPerson (no FluentCustomer).

¿Cómo suele resolverse este problema?

Author: bniwredyc, 2010-02-17

6 answers

Puede usar genéricos para lograr eso.

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

Y ahora:

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-17 13:51:22

Trate de usar algunos métodos de extensión.

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

Después puedes usar como

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-17 07:39:55

Lógicamente necesitas configurar cosas de la más específica (cliente) a la menos específica (persona) o de lo contrario es incluso difícil de leer a pesar de la interfaz fluida. Siguiendo esta regla en la mayoría de los casos no tendrás que meterte en problemas. Sin embargo, si por alguna razón todavía necesita mezclarlo, puede usar declaraciones de énfasis intermedias como

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-17 06:47:05

Una solución donde necesita una interfaz fluida, herencia y también algunos genéricos...

De todos modos, como he dicho antes: esta es la única opción si desea usar herencia y acceder también a miembros protegidos...

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/ajaxhispano.com/template/agent.layouts/content.php on line 61
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);
       }
   }

E invocarlo usando

  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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-17 07:08:21

¿Es una interfaz fluida realmente la mejor llamada aquí, o sería mejor un inicializador?

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

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

A diferencia de una interfaz fluida, esto funciona bien sin métodos heredados que devuelvan la clase base y arruinen la cadena. Cuando se hereda una propiedad, a la persona que llama realmente no le debería importar si FirstName se implementó primero en Persona o Cliente u Objeto.

Encuentro esto más legible también, ya sea en una línea o múltiple, y no tienes que pasar por la molestia de proporcionando funciones de auto-decoración fluidas que corresponden con cada propiedad.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-17 07:10:30