2010-02-17 12 views
53

Voy a mostrar un problema con el ejemplo. Hay una clase base con interfaz fluida:Interfaces fluidas y herencia en C#

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 se llama a customer.WithAccountNumber("000").WithFirstName("John").WithLastName("Smith") no se puede agregar .WithId(123) al final porque el tipo de retorno de la El método WithLastName() es FluentPerson (no FluentCustomer).

¿Cómo se resuelve este problema habitualmente?

+4

¡Hilo interesante!La pregunta y las respuestas dadas, que van desde la desactivación de la herencia (Ramesh), la creación de un método de extensión que convierte Person to Customer (Dzimtry), el uso interesante de Yann de Generics para la clase "base", RichardTallent dividiéndolo en dos clases separadas con campos duplicados, y los comentarios de Gorpik: todos han aumentado la convicción que tengo de que debería evitar programar con interfaces fluidas. Para mí, la "belleza" del encadenamiento de métodos no justificaría las "contorsiones del código yóguico" aquí propuestas. Pero estoy dispuesto a "comer mis palabras", como siempre :) – BillW

+1

@BillW: tienes razón. En realidad, hice esa pregunta porque ya tengo una clase con una interfaz fluida y necesito implementar otra clase que use mucha funcionalidad de la primera clase. Un requisito más es no romper el código anterior. Creo que la interfaz fluida no es una cosa universal y puede vivir sin ella, pero en algunos casos son realmente útiles (no es necesario). Por cierto, me sorprendió la variedad de enfoques sugeridos en las respuestas también. – bniwredyc

Respuesta

38

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(); 
+3

Se ve bien, pero no puede heredar más lejos de 'FluentCustomer'. – Gorpik

+1

¿Cómo se propone crear una instancia de FluentPerson? =) – Steck

+2

@Gorpik; Buen punto. De hecho, creo que debería ser posible heredar más, pero la sintaxis sería probablemente fea (con genéricos anidados). De todos modos, funciona bien para escenarios simples; por ejemplo, cuando necesita construir varios constructores especializados encima de uno abstracto común. –

4

Lógicamente, debe configurar las cosas desde el más específico (cliente) al menos específico (persona) o de lo contrario es incluso difícil de leer a pesar de la fluida interfaz. Siguiendo esta regla en la mayoría de los casos no necesitarás meterse en problemas. Sin embargo, si por alguna razón usted todavía necesita mezclarlo puede utilizar declaraciones enfatizando intermedios como

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

customer.WIthLastName("Bob").AsCustomer().WithId(10); 
3
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); 
     } 
    } 

Y invocarlo usando

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

Interesante: ahora ha eliminado la herencia de la clase FluentCustomer de la clase FluentPerson. – BillW

+0

@BillW, siempre es bueno implementar la herencia usando la interfaz que la herencia de clase (aunque no lo he intentado en mi respuesta, pero es simple hacer eso). – RameshVel

+0

Estoy fascinado con su solución en el sentido de que, en lugar de mirar desde la perspectiva de "Cliente" "isA" "Persona" (herencia), me parece mirar desde una perspectiva en la que "Cliente" "tiene-a" Persona (lástima que no soy lo suficientemente brillante como para saber si eso es "composición" :). Namaste, o Vannakum, si es más apropiado. – BillW

3

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

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 hereda una propiedad, la persona que llama realmente no debería preocuparse de si FirstName se implementó por primera vez en Persona, Cliente u Objeto.

Encuentro esto más legible también, ya sea en una línea o múltiple, y, no tiene que tomarse la molestia de proporcionar funciones de auto-decoración fluidas que se correspondan con cada propiedad.

+0

Creo que OP solo estaba usando un ejemplo simple para ilustrar el problema. – Gorpik

+1

"Caballos por cursos", creo. Añadiendo al comentario anterior de Gorpik, el uso de una interfaz fluida le permitiría combinar setters con otros elementos de comportamiento si fueran implementados, mientras que los inicializadores lo limitarían solo a establecer una colección fija de valores, y requerirían sobrecargas o algún otro constructo para elementos opcionales :-) –

+0

Los elementos del inicializador son * todo * opcionales y * cualquier propiedad de * escritura pública se puede inicializar. Sin necesidad de sobrecargas Pero para establecer comportamientos más complejos en lugar de solo una combinación corta de iniciadores primitivos), tiene razón: las funciones fluidas son útiles. – richardtallent

34

tratar de utilizar algunos métodos Extention.

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 puede utilizar como

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

Me gusta más este enfoque que las otras ideas propuestas simplemente porque ofrecería la mayor flexibilidad en términos de extensibilidad. ¡Por supuesto, el inconveniente sería si la versión .NET de destino del OP no lo admite! –

+2

Upvoted, también me gusta más, si se requieren métodos fluidos. Por supuesto, el inconveniente es que los métodos de extensión solo pueden acceder a los miembros públicos del objeto, por lo que el método de extensión con fluidez puede necesitar llamar a un método tradicional en el objeto (para que se pueda realizar el trabajo privado) y luego devolver el objeto. – richardtallent

+1

Usar el método de extensión fluido NO es una opción si tiene dos jerarquías de clases independientes con la misma propiedad. – Smartkid

4

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

De todos modos, como dije antes: este es el única opción si desea utilizar la herencia y acceder también a los 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); 
    } 
} 
+0

Impresionante, estaba buscando algo como esto. Era más difícil ver cómo se implementaría este tipo de solución con una base genérica. –

Cuestiones relacionadas