2009-04-23 14 views
12

Tengo una clase que tengo que llamar uno o dos métodos muchas veces después de la otra. Los métodos actualmente devuelven void. Estaba pensando, ¿sería mejor devolverlo this, para que los métodos se puedan anidar? ¿o eso se considera muy, muy, muy malo? o si es malo, ¿sería mejor si devuelve un objeto nuevo del mismo tipo? ¿O qué piensas? Como ejemplo he creado tres versiones de una clase sumador:C#: ¿Devolviendo 'esto' para el método de anidar?

// Regular 
class Adder 
{ 
    public Adder() { Number = 0; } 

    public int Number { get; private set; } 

    public void Add(int i) { Number += i; } 
    public void Remove(int i) { Number -= i; } 
} 

// Returning this 
class Adder 
{ 
    public Adder() { Number = 0; } 

    public int Number { get; private set; } 

    public Adder Add(int i) { Number += i; return this; } 
    public Adder Remove(int i) { Number -= i; return this; } 
} 

// Returning new 
class Adder 
{ 
    public Adder() : this(0) { } 
    private Adder(int i) { Number = i; } 

    public int Number { get; private set; } 

    public Adder Add(int i) { return new Adder(Number + i); } 
    public Adder Remove(int i) { return new Adder(Number - i); } 
} 

La primera de ellas se puede utilizar de esta manera:

var a = new Adder(); 
    a.Add(4); 
    a.Remove(1); 
    a.Add(7); 
    a.Remove(3); 

Los otros dos se puede utilizar de esta manera:

var a = new Adder() 
     .Add(4) 
     .Remove(1) 
     .Add(7) 
     .Remove(3); 

Donde la única diferencia es que a en el primer caso es el new Adder() mientras que en el último es el resultado del último método.

Lo primero que encuentro es que rápidamente se vuelve ... molesto para escribir una y otra vez. Entonces me gustaría usar una de las otras versiones.

El tercero funciona como muchos otros métodos, como muchos métodos String y métodos de extensión IEnumerable. Supongo que tiene su lado positivo porque puedes hacer cosas como var a = new Adder(); var b = a.Add(5); y luego tener una que era 0 y una que era 5. Pero al mismo tiempo, ¿no es un poco caro crear nuevos objetos todo el tiempo? ¿Y cuándo morirá el primer objeto? Cuando el primer método devuelve un tipo de? ¿O?

De todos modos, me gusta el que devuelve this y creo que usaré eso, pero tengo mucha curiosidad por saber qué piensan los demás acerca de este caso. Y lo que se considera la mejor práctica.

+1

Eres de tercera clase y no haces lo que dijiste que hace. Agregaría o restaría de Number y lo pasaría a un nuevo Adder (...) como un entero por separado. – Samuel

+0

¡oh, buena captura! thanks =) – Svish

Respuesta

17

El estilo 'devolver esto' a veces se llama fluent interface y es una práctica común.

+0

aha. guay. entonces supongo que iré por eso, entonces =) – Svish

+2

Tenga en cuenta que el estilo fluido escribe de manera similar a una alternativa muy diferente, que es hacer que su objeto sea inmutable y devolver nuevas instancias de su objeto en lugar de modificar el estado interno. Con una clase simple como la del ejemplo (no es que el ejemplo sea muy realista) es posible que desee considerar los pros y los contras de simplemente hacerlo inmutable. – mquander

+2

Ambos 'return this' y' return new' son ejemplos con diferentes aplicaciones: jQuery '$ ('# eleId'). Show()' devuelve 'this' porque de lo contrario crearía una copia de eleId. Linq 'myCollection.Where (x => x.IsValid)' devuelve un nuevo objeto porque, de lo contrario, habría cambiado myCollection, que podría ser inmutable. – Keith

0

Considera esto: si vuelves a este código en 5 años, ¿va a tener sentido para ti? Si es así, entonces supongo que puedes seguir adelante.

Para este ejemplo específico, sin embargo, parecería que la sobrecarga de los operadores + y - haría las cosas más claras y lograría lo mismo.

+0

Bueno, por supuesto: p En este caso, lo mejor habría sido utilizar un tipo int directamente. Pero fue solo lo que pude encontrar como un simple ejemplo de métodos anidados. Mi objetivo era llamar al método, no lo que realmente hacían: p – Svish

+0

Excepto cuando regresas en 5 años. Una cadena de métodos se revelará instantáneamente con un mouse sobre hover, mientras que los operadores requieren mirar la clase en sí. – Samuel

+0

ah, ese es un buen punto sí. – Svish

6

Me gusta la "sintaxis fluida" y tomaría la segunda. Después de todo, todavía puede usarlo como el primero, para las personas que se sienten incómodas con la sintaxis fluida.

otra idea para hacer una interfaz como los sumadores uno más fácil de usar:

public Adder Add(params int[] i) { /* ... */ } 
public Adder Remove(params int[] i) { /* ... */ } 

Adder adder = new Adder() 
    .Add(1, 2, 3) 
    .Remove(3, 4); 

Siempre trato de hacer que las interfaces de corto y fácil de leer, pero muchas personas les gusta escribir el código tan complicado como posible.

+0

Idea genial con el uso de params. Podría usarlo yo mismo .... – Svish

0
  1. Para su caso específico, la sobrecarga de los operadores aritméticos sería probablemente la mejor solución.

  2. De vuelta this (Interfaz fluida) es una práctica común para crear expresiones: las pruebas unitarias y los marcos de burla usan mucho. Fluido Hibernate es otro ejemplo.

  3. La devolución de una nueva instancia también puede ser una buena opción. Te permite hacer que tu clase sea inmutable; en general, es algo bueno y muy útil en el caso del multihilo. Pero piense en la sobrecarga de creación de objetos si la inmutabilidad no le sirve.

+0

Muy bien resumido =) – Svish

0

Si lo llamas Adder, me gustaría ir con esto. Sin embargo, es extraño que una clase de Adder contenga una respuesta.

Puede considerar hacer algo así como MyNumber y crear un método Add().

Lo ideal sería que (en mi humilde opinión), eso no cambiaría el número que se almacena dentro de la instancia, pero crear una nueva instancia con el nuevo valor, que regresa:

class MyNumber 
{ 
    ... 

    MyNumber Add(int i) 
    { 
     return new MyNumber(this.Value + i); 
    } 
} 
+0

Bueno, por supuesto. Y aún más extraño que contenga un método llamado Remove, que en realidad debería haber sido llamado Restar. Pero ese no era el punto aquí: p – Svish

0

creo que para las interfaces simples, la interfaz "fluida" es muy útil, particularmente porque es muy simple de implementar. El valor de la interfaz fluida es que elimina una gran cantidad de pelusas extrañas que se interponen en el camino de la comprensión. Desarrollar una interfaz de este tipo puede llevar mucho tiempo, especialmente cuando la interfaz comienza a estar involucrada. Debería preocuparse por cómo "lee" el uso de la interfaz; En mi opinión, el uso más convincente para tal interfaz es cómo comunica la intención del programador, no la cantidad de caracteres que guarda.

Para responder a su pregunta específica, me gusta el "devolver este" estilo. Mi uso típico de la interfaz fluida es definir un conjunto de opciones. Es decir, creo una instancia de la clase y luego uso los métodos fluidos en la instancia para definir el comportamiento deseado del objeto. Si tengo una opción sí/no (digamos para el registro), trato de no tener un método "setLogging (bool state)", sino dos métodos "WithLogging" y "WithoutLogging". Esto es algo más de trabajo, pero la claridad del resultado final es muy útil.

2

El encadenamiento es algo bueno de tener y es esencial en algunos marcos (por ejemplo, las extensiones de Linq y jQuery lo usan mucho).

Si se crea un nuevo objeto o return this depende de cómo se espera que su objeto inicial de comportarse:

var a = new Adder(); 

var b = a.Add(4) 
     .Remove(1) 
     .Add(7) 
     .Remove(3); 

//now - should a==b ? 

encadenamiento en jQuery habrá cambiado su objeto original - se ha vuelto esto. Ese es el comportamiento esperado; de lo contrario, básicamente se clonarían los elementos de la interfaz de usuario.

Encadenamiento en Linq habrá dejado su colección original sin cambios. Ese también es el comportamiento esperado: cada función encadenada es un filtro o transformación, y la colección original a menudo es inmutable.

qué patrón mejor se adapte a lo que estás haciendo?

+0

Buenas observaciones sobre el pensamiento detrás de Linq y jQuery =) En el asunto de "should a == b", en este caso tonto exacto, deberían por supuesto considerarse iguales ya que tienen el mismo numero. Pero dependiendo de si usaste el método 2 o 3, las referencias realmente no necesitarían ser iguales. – Svish

+0

Si usa 'new' cada vez después de las operaciones, complete a.Number == 1 while b.Number == 7. Si usa' this' en su lugar también a.Number == 7 también. – Keith

0

La principal diferencia entre la segunda y la tercera solución es que al devolver una nueva instancia en lugar de esto, puede "atrapar" el objeto en un cierto estado y continuar desde allí.

var a = new Adder() .Add (4);

var b = a.Remove (1);

var c = a.Add (7) .Eliminar (3);

En este caso, ambos byc tienen el estado capturado en a como punto de partida. Me encontré con este idioma mientras leía sobre un patrón para crear objetos de dominio de prueba en Growing Object-Oriented Software, Guided by Tests by Steve Freeman; Nat Pryce.

Sobre su pregunta con respecto a la duración de sus instancias: Yo esperaría que sean elegibles para la recolección de basura tan pronto como la invocación de Eliminar o Agregar esté regresando.