Cuando usar el encadenamiento
El encadenamiento de funciones es muy popular en los idiomas en los que un IDE con autocompletar es un lugar común. Por ejemplo, casi todos los desarrolladores de C # usan Visual Studio. Por lo tanto, si está desarrollando con C #, agregar encadenamiento a sus métodos puede ser un ahorro de tiempo para los usuarios de esa clase porque Visual Studio lo ayudará a construir la cadena.
Por otro lado, los lenguajes como PHP que son de naturaleza altamente dinámica y que a menudo no tienen soporte de autocompletar en IDEs verán menos clases que admitan el encadenamiento. El encadenamiento solo será apropiado cuando se empleen los phpDoc correctos para exponer los métodos encadenables.
¿Qué es el encadenamiento?
Dada una clase llamada Foo
los siguientes dos métodos son ambos encadenables.
function what() { return this; }
function when() { return new Foo(this); }
El hecho de que uno sea una referencia a la instancia actual y cree una nueva instancia no cambia que estos son métodos encadenables.
No existe una regla de oro de que un método encadenable solo debe hacer referencia al objeto actual. De hecho, los métodos encadenables pueden ser a través de dos clases diferentes. Por ejemplo;
class B { function When() { return true; } };
class A { function What() { return new B(); } };
var a = new A();
var x = a.What().When();
No hay ninguna referencia a this
ninguno de los ejemplos anteriores. El código a.What().When()
es un ejemplo de encadenamiento. Lo interesante es que el tipo de clase B
nunca se asigna a una variable.
Un método se encadena cuando su valor de retorno se utiliza como el siguiente componente de una expresión.
Aquí hay un ejemplo más
// return value never assigned.
myFile.Open("something.txt").Write("stuff").Close();
// two chains used in expression
int x = a.X().Y() * b.X().Y();
// a chain that creates new strings
string name = str.Substring(1,10).Trim().ToUpperCase();
Cuando usar this
ynew(this)
Las cadenas en la mayoría de los idiomas son inmutables. Por lo tanto, las llamadas a métodos de encadenamiento siempre generan nuevas cadenas. Donde como un objeto como StringBuilder se puede modificar.
La consistencia es la mejor práctica.
Si tiene métodos que modifican el estado de un objeto y lo devuelven this
, no mezcle métodos que devuelvan nuevas instancias. En su lugar, cree un método específico llamado Clone()
que haga esto explícitamente.
var x = a.Foo().Boo().Clone().Foo();
Eso es mucho más claro en cuanto a lo que está sucediendo dentro a
.
El paso afuera y el truco de vuelta
A esto lo llamo el truco de salida y retroceso , porque resuelve muchos problemas comunes relacionados con el encadenamiento. Básicamente significa que sales de la clase original a una nueva clase temporal y luego regresas a la clase original.
La clase temporal existe solo para proporcionar características especiales a la clase original, pero solo bajo condiciones especiales.
A menudo hay momentos en que una cadena necesita cambiar de estado , pero la clase A
no puede representar todos esos estados posibles . Entonces, durante una cadena, se introduce una nueva clase que contiene una referencia a A
. Esto permite al programador entrar en un estado y volver a A
.
Aquí está mi ejemplo, que el estado especial se conozca como B
.
class A {
function Foo() { return this; }
function Boo() { return this; }
function Change() return new B(this); }
}
class B {
var _a;
function (A) { _a = A; }
function What() { return this; }
function When() { return this; }
function End() { return _a; }
}
var a = new A();
a.Foo().Change().What().When().End().Boo();
Ahora ese es un ejemplo muy simple. Si quisieras tener más control, entonces B
podrías volver a un nuevo supertipo A
que tenga métodos diferentes.
return this
. ¿Cómo puedo ayudar a los usuarios de mis bibliotecas a manejar y comprender esta situación? (Permitiéndoles encadenar métodos, incluso cuando no es necesario, ¿esto realmente estaría bien? ¿O debería seguir solo de una manera, exigir un cambio?)Dependiendo del idioma, tener métodos que devuelvan
void
/unit
y modifiquen su instancia o parámetros no es idiomático. Incluso en lenguajes que solían hacer eso más (C #, C ++), está pasando de moda con el cambio hacia una programación de estilo más funcional (objetos inmutables, funciones puras). Pero supongamos que hay una buena razón por ahora.Para ciertos comportamientos (pensar
x++
) se espera que la operación devuelva el resultado, aunque modifique la variable. Pero esa es en gran medida la única razón para hacerlo por su cuenta.Depende.
En los lenguajes donde es común copiar / nuevo y devolver (C # LINQ y cadenas), devolver la misma referencia sería confuso. En los lenguajes donde modificar y devolver es común (algunas bibliotecas de C ++), copiar sería confuso.
Hacer que la firma sea inequívoca (devolviendo
void
o usando construcciones de lenguaje como propiedades) sería la mejor manera de aclarar. Después de eso, dejar en claro el nombreSetFoo
para mostrar que está modificando la instancia existente es bueno. Pero la clave es mantener las expresiones idiomáticas del idioma / biblioteca con la que está trabajando.fuente
Clear()
oAdd()
de cualquier tipo de colección modificarán la misma instancia y devolveránvoid
. En ambos casos, dices que sería confuso ...obj = myCollection.Where(x => x.MyProperty > 0).OrderBy(x => x.MyProperty);
entonces?Where()
devuelve un nuevo objeto de colección.OrderBy()
incluso devuelve un objeto de colección diferente, porque el contenido está ordenado.IEnumerable
, pero para ser claro, no son copias, y no son colecciones (a excepción deToList
,ToArray
, etc.).ICollection
. Culpa mía.(Asumí C ++ como su lenguaje de programación)
Para mí, este es principalmente un aspecto de legibilidad. Si A, B, C son modificadores, especialmente si este es un objeto temporal pasado como parámetro a alguna función, por ejemplo
comparado con
Revisando si está bien devolver una referencia a la instancia modificada, diría que sí, y le diré, por ejemplo, a los operadores de flujo '>>' y '<<' ( http://www.cprogramming.com/tutorial/ operator_overloading.html )
fuente
También podría hacer el método de encadenamiento con copia de devolución en lugar de modificar la devolución.
Un buen ejemplo de C # es string.Replace (a, b) que no cambia la cadena en la que se invocó, sino que devuelve una nueva cadena que le permite encadenar alegremente.
fuente