Encadenamiento de métodos: ¿por qué es una buena práctica o no?


151

El encadenamiento de métodos es la práctica de métodos de objetos que devuelven el objeto en sí mismo para que el resultado se llame a otro método. Me gusta esto:

participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()

Esto parece ser considerado una buena práctica, ya que produce código legible o una "interfaz fluida". Sin embargo, para mí, en cambio, parece romper la notación de llamada de objeto implícita en la orientación del objeto en sí misma: el código resultante no representa la realización de acciones en el resultado de un método anterior, que es cómo generalmente se espera que funcione el código orientado a objetos:

participant.getSchedule('monday').saveTo('monnday.file')

Esta diferencia se las arregla para crear dos significados diferentes para la notación de punto de "llamar al objeto resultante": en el contexto de encadenamiento, el ejemplo anterior se leería como guardar el objeto participante , a pesar de que el ejemplo tiene la intención de guardar la programación objeto recibido por getSchedule.

Entiendo que la diferencia aquí es si se debe esperar que el método llamado devuelva algo o no (en cuyo caso devolvería el objeto llamado en sí mismo para encadenarlo). Pero estos dos casos no se distinguen de la notación en sí, solo de la semántica de los métodos que se llaman. Cuando no se usa el método de encadenamiento, siempre puedo saber que una llamada de método opera en algo relacionado con el resultado de la llamada anterior; con el encadenamiento, esta suposición se rompe y tengo que procesar semánticamente toda la cadena para comprender cuál es el objeto real llamado realmente es. Por ejemplo:

participant.attend(event).setNotifications('silent').getSocialStream('twitter').postStatus('Joining '+event.name).follow(event.getSocialId('twitter'))

Allí, las dos últimas llamadas al método se refieren al resultado de getSocialStream, mientras que las anteriores se refieren al participante. Tal vez sea una mala práctica escribir cadenas donde el contexto cambia (¿es así?), Pero incluso entonces tendrá que verificar constantemente si las cadenas de puntos que se parecen son en realidad dentro del mismo contexto, o solo funcionan en el resultado .

Para mí, parece que si bien el método de encadenamiento superficial produce código legible, sobrecargar el significado de la notación de puntos solo genera más confusión. Como no me considero un gurú de la programación, supongo que la culpa es mía. Entonces: ¿qué me estoy perdiendo? ¿Entiendo el método de encadenamiento de alguna manera incorrecto? ¿Hay algunos casos en los que el encadenamiento de métodos es especialmente bueno o algunos en los que es especialmente malo?

Nota al margen: Entiendo que esta pregunta podría leerse como una declaración de opinión enmascarada como una pregunta. Sin embargo, no lo es: realmente quiero entender por qué el encadenamiento se considera una buena práctica, y dónde me equivoco al pensar que rompe la notación inherente orientada a objetos.


Parece que el método de encadenamiento es al menos una forma de escribir código inteligente en Java. Incluso si no todo el mundo está de acuerdo ..
Mercer Traieste

También llaman a estos métodos "fluidos" o una interfaz "fluida". Es posible que desee actualizar su título para usar este término.
S.Lott

44
En otra discusión de SO se dijo que la interfaz fluida es un concepto más amplio que tiene que ver con la lectura del código, y el encadenamiento de métodos es solo una forma de apuntar hacia esto. Sin embargo, están estrechamente relacionados, así que agregué la etiqueta y las interfaces fluidas referenciadas en el texto; creo que son suficientes.
Ilari Kajaste

14
La forma en que he llegado a pensar en esto es que el método de encadenamiento es, de hecho, un truco para obtener una característica faltante en la sintaxis del lenguaje. Realmente no sería necesario si hubiera una notación alternativa incorporada .que ignorara cualquier valor de retorno de mehtod y siempre invocara cualquier método encadenado usando el mismo objeto.
Ilari Kajaste

Es un gran lenguaje de codificación, pero como todas las excelentes herramientas, se abusa de él.
Martin Spamer

Respuestas:


74

Estoy de acuerdo en que esto es subjetivo. En su mayor parte, evito el encadenamiento de métodos, pero recientemente también encontré un caso en el que era justo lo correcto: tenía un método que aceptaba algo así como 10 parámetros y necesitaba más, pero durante la mayor parte del tiempo solo tenía que especificar un pocos. Con las anulaciones esto se volvió muy engorroso muy rápido. En cambio, opté por el enfoque de encadenamiento:

MyObject.Start()
    .SpecifySomeParameter(asdasd)
    .SpecifySomeOtherParameter(asdasd)
    .Execute();

El método de encadenamiento de métodos era opcional, pero facilitaba la escritura de código (especialmente con IntelliSense). Tenga en cuenta que este es un caso aislado y no es una práctica general en mi código.

El punto es que, en el 99% de los casos, probablemente pueda hacerlo igual o mejor sin encadenar el método. Pero existe el 1% donde este es el mejor enfoque.


44
En mi opinión, la mejor manera de usar el encadenamiento de métodos en este escenario es crear un objeto de parámetro para pasar a la función, como P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);. Tiene la ventaja de poder reutilizar este objeto de parámetro en otras llamadas, lo cual es una ventaja
pedromanoel

20
Solo mi centavo de contribución sobre los patrones, el método de fábrica generalmente tiene un solo punto de creación y los productos son una elección estática basada en los parámetros del método de fábrica. La creación de la cadena se parece más a un patrón de Generador donde se llaman diferentes métodos para obtener un resultado, los métodos pueden ser opcionales como en el Encadenamiento de métodos , podemos tener algo así como PizzaBuilder.AddSauce().AddDough().AddTopping()más referencias aquí
Marco Medrano

3
El método de encadenamiento (como se describe en la pregunta original) se considera malo cuando viola la ley de demeter . Ver: ifacethoughts.net/2006/03/07/… La respuesta dada aquí de hecho sigue esa ley, ya que es un "patrón de construcción".
Angel O'Sphere

2
@Marco Medrano El ejemplo de PizzaBuilder siempre me molestó desde que lo leí en JavaWorld hace años. Siento que debería agregar salsa a mi pizza, no a mi chef.
Breandán Dalton

1
Sé a qué te refieres, Vilx. Sin embargo, cuando leí list.add(someItem), lo leí como "este código se agrega someItemal listobjeto". así que cuando leo PizzaBuilder.AddSauce(), leo esto naturalmente como "este código está agregando salsa al PizzaBuilderobjeto". En otras palabras, veo al orquestador (el que hace la adición) como el método en el que se incrusta el código list.add(someItem)o PizzaBuilder.addSauce(). Pero creo que el ejemplo de PizzaBuilder es un poco artificial. Tu ejemplo de MyObject.SpecifySomeParameter(asdasd)trabajos funciona bien para mí.
Breandán Dalton

78

Solo mis 2 centavos;

El encadenamiento de métodos dificulta la depuración: - No puede poner el punto de interrupción en un punto conciso, por lo que puede pausar el programa exactamente donde lo desea - Si uno de estos métodos arroja una excepción y obtiene un número de línea, no tiene idea qué método en la "cadena" causó el problema.

Creo que generalmente es una buena práctica escribir siempre líneas muy cortas y concisas. Cada línea solo debe hacer una llamada al método. Prefiere más líneas a líneas más largas.

EDITAR: el comentario menciona que el método de encadenamiento y salto de línea están separados. Eso es verdad. Sin embargo, dependiendo del depurador, puede o no ser posible colocar un punto de interrupción en el medio de una declaración. Incluso si puede, el uso de líneas separadas con variables intermedias le brinda mucha más flexibilidad y una gran cantidad de valores que puede examinar en la ventana Watch que ayuda al proceso de depuración.


44
¿No son independientes el uso de nuevas líneas y el encadenamiento de métodos? Puede encadenar con cada llamada en una nueva línea según @ Vilx- answer, y normalmente puede poner varias declaraciones separadas en la misma línea (por ejemplo, utilizando punto y coma en Java).
brabster

2
Esta respuesta está completamente justificada, pero solo muestra una debilidad presente en todos los depuradores que conozco y no está relacionada específicamente con la pregunta.
masterxilo

1
@Brabster. Pueden estar separados para ciertos depuradores, sin embargo, tener llamadas separadas con variables intermedias aún le proporciona una gran cantidad de información mientras investiga errores.
RAYO

44
+1, en ningún momento sabe qué devolvió cada método al depurar por pasos una cadena de métodos. El patrón de cadena de método es un truco. Es la peor pesadilla de un programador de mantenimiento.
Lee Kowalkowski

1
Tampoco soy un gran fanático del encadenamiento, pero ¿por qué no simplemente poner un punto de interrupción dentro de las definiciones de métodos?
Pankaj Sharma

39

Personalmente, prefiero encadenar métodos que solo actúan sobre el objeto original, por ejemplo, establecer varias propiedades o llamar a métodos de tipo utilidad.

foo.setHeight(100).setWidth(50).setColor('#ffffff');
foo.moveTo(100,100).highlight();

No lo uso cuando uno o más de los métodos encadenados devolverían cualquier objeto que no sea foo en mi ejemplo. Mientras que sintácticamente puede encadenar cualquier cosa siempre que esté utilizando la API correcta para ese objeto en la cadena, cambiar los objetos en mi humilde opinión hace que las cosas sean menos legibles y puede ser realmente confuso si las API para los diferentes objetos tienen alguna similitud. Si usted hace algo de llamada a un método muy común en el extremo ( .toString(), .print(), lo que sea) el objeto que está en última instancia, que actúan sobre? Alguien que lee el código casualmente podría no darse cuenta de que sería un objeto devuelto implícitamente en la cadena en lugar de la referencia original.

Encadenar diferentes objetos también puede conducir a errores nulos inesperados. En mis ejemplos, suponiendo que foo es válido, todas las llamadas a métodos son "seguras" (por ejemplo, válidas para foo). En el ejemplo del OP:

participant.getSchedule('monday').saveTo('monnday.file')

... no hay garantía (como desarrollador externo que mira el código) de que getSchedule realmente devuelva un objeto de programación válido y no nulo. Además, la depuración de este estilo de código suele ser mucho más difícil, ya que muchos IDE no evaluarán la llamada al método en el momento de la depuración como un objeto que puede inspeccionar. En mi opinión, cada vez que necesite un objeto para inspeccionar con fines de depuración, prefiero tenerlo en una variable explícita.


Si existe la posibilidad de que un Participantno tenga un método válido Schedule, el getSchedulemétodo está diseñado para devolver un Maybe(of Schedule)tipo y el saveTométodo está diseñado para aceptar un Maybetipo.
Lightman

24

Martin Fowler tiene una buena discusión aquí:

Método de encadenamiento

Cuando usarlo

Method Chaining puede agregar mucho a la legibilidad de un DSL interno y, como resultado, en algunas mentes se ha convertido casi en un sinónimo de DSL interno. Sin embargo, el método de encadenamiento es mejor cuando se usa junto con otras combinaciones de funciones.

Method Chaining es particularmente efectivo con gramáticas como parent :: = (this | that) *. El uso de diferentes métodos proporciona una forma legible de ver qué argumento vendrá después. Del mismo modo, los argumentos opcionales se pueden omitir fácilmente con el método de encadenamiento. Una lista de cláusulas obligatorias, como parent :: = first second no funciona tan bien con la forma básica, aunque puede ser compatible con el uso de interfaces progresivas. La mayoría de las veces preferiría la función anidada para ese caso.

El mayor problema para Method Chaining es el problema de acabado. Si bien existen soluciones alternativas, por lo general, si se encuentra con esto, es mejor que utilice una función anidada. La función anidada también es una mejor opción si te estás metiendo en un lío con las variables de contexto.


2
El enlace está muerto :(
Qw3ry

¿Qué quieres decir con DSL? Lenguaje específico del dominio
Sören

@ Sören: Fowler hace referencia a lenguajes específicos de dominio.
Dirk Vollmar

21

En mi opinión, el método de encadenamiento es una novedad. Claro, se ve genial, pero no veo ninguna ventaja real en ello.

Como es:

someList.addObject("str1").addObject("str2").addObject("str3")

mejor que:

someList.addObject("str1")
someList.addObject("str2")
someList.addObject("str3")

La excepción podría ser cuando addObject () devuelve un nuevo objeto, en cuyo caso el código desencadenado puede ser un poco más engorroso como:

someList = someList.addObject("str1")
someList = someList.addObject("str2")
someList = someList.addObject("str3")

9
Es más conciso, ya que evita dos de las partes 'someList' incluso en su primer ejemplo y termina con una línea en lugar de tres. Ahora, si eso es realmente bueno o malo, depende de diferentes cosas y quizás sea una cuestión de gustos.
Fabian Steeg

26
La verdadera ventaja de tener 'someList' solo una vez, es que es mucho más fácil darle un nombre más largo y descriptivo. Cada vez que un nombre debe aparecer varias veces en una sucesión rápida, hay una tendencia a acortarlo (para reducir la repetición y mejorar la legibilidad), lo que lo hace menos descriptivo y perjudica la legibilidad.
Chris Dodd

Un buen punto sobre la legibilidad y los principios DRY con la advertencia de que el encadenamiento de métodos evita la introspección del valor de retorno de un método determinado y / o implica una presunción de listas / conjuntos vacíos y valores no nulos de cada método en la cadena (una falacia que causa muchos NPE en sistemas tengo que depurar / corregir a menudo).
Darrell Teague el

@ChrisDodd ¿Nunca has oído hablar de la finalización automática?
inf3rno

1
"el cerebro humano es muy bueno para reconocer la repetición de texto si tiene la misma sangría", ese es precisamente el problema con la repetición: hace que el cerebro se concentre en el patrón repetitivo. Entonces, para leer Y ENTENDER el código, debe forzar a su cerebro a mirar más allá / detrás del patrón repetitivo para ver qué está sucediendo realmente, que está en las diferencias entre el patrón repetitivo que su cerebro tiende a pasar por alto. Esto es POR QUÉ la repetición es tan mala para la legibilidad del código.
Chris Dodd

7

Es peligroso porque puede depender de más objetos de los esperados, como entonces su llamada devuelve una instancia de otra clase:

Daré un ejemplo:

foodStore es un objeto que se compone de muchas tiendas de alimentos que posee. foodstore.getLocalStore () devuelve un objeto que contiene información sobre la tienda más cercana al parámetro. getPriceforProduct (cualquier cosa) es un método de ese objeto.

Entonces, cuando llamas a foodStore.getLocalStore (parámetros) .getPriceforProduct (cualquier cosa)

no solo depende de FoodStore como lo hace, sino también de LocalStore.

Si getPriceforProduct (cualquier cosa) cambia alguna vez, debe cambiar no solo FoodStore sino también la clase que llamó al método encadenado.

Siempre debe apuntar a un acoplamiento flojo entre clases.

Dicho esto, personalmente me gusta encadenarlos cuando programo Ruby.


7

Muchos utilizan el encadenamiento de métodos como una forma de conveniencia en lugar de tener en cuenta cualquier preocupación de legibilidad. El encadenamiento de métodos es aceptable si implica realizar la misma acción en el mismo objeto, pero solo si realmente mejora la legibilidad, y no solo por escribir menos código.

Desafortunadamente, muchos utilizan el método de encadenamiento según los ejemplos dados en la pregunta. Mientras que pueden todavía resultar legibles, que están causando por desgracia alto acoplamiento entre varias clases, lo que no es deseable.


6

Esto parece un poco subjetivo.

El método de encadenamiento no es algo inherentemente malo o bueno.

La legibilidad es lo más importante.

(También considere que tener un gran número de métodos encadenados hará que las cosas sean muy frágiles si algo cambia)


De hecho, podría ser subjetivo, de ahí la etiqueta subjetiva. Lo que espero que las respuestas me destaquen es en qué casos el encadenamiento de métodos sería una buena idea; en este momento no veo mucho, pero creo que esto es solo mi incapacidad para comprender los puntos buenos del concepto, en lugar de algo inherentemente malo en el encadenamiento mismo.
Ilari Kajaste

¿No sería inherentemente malo si resulta en un alto acoplamiento? Romper la cadena en declaraciones individuales no reduce la legibilidad.
aberrant80

1
depende de cuán dogmático quieras ser. Si resulta en algo más legible, entonces esto podría ser preferible en muchas situaciones. El gran problema que tengo con este enfoque es que la mayoría de los métodos en un objeto devolverán referencias al objeto en sí, pero a menudo el método devolverá una referencia a un objeto hijo sobre el cual puede encadenar más métodos. Una vez que comienzas a hacer esto, se vuelve muy difícil para otro programador desenredar lo que está sucediendo. Además, cualquier cambio en la funcionalidad de un método será difícil de depurar en una declaración compuesta grande.
John Nicholas

6

Beneficios del encadenamiento
, es decir, donde me gusta usarlo

Un beneficio del encadenamiento que no vi mencionado fue la capacidad de usarlo durante el inicio variable, o al pasar un nuevo objeto a un método, no estoy seguro de si es una mala práctica o no.

Sé que este es un ejemplo artificial, pero digamos que tiene las siguientes clases

Public Class Location
   Private _x As Integer = 15
   Private _y As Integer = 421513

   Public Function X() As Integer
      Return _x
   End Function
   Public Function X(ByVal value As Integer) As Location
      _x = value
      Return Me
   End Function

   Public Function Y() As Integer
      Return _y
   End Function
   Public Function Y(ByVal value As Integer) As Location
      _y = value
      Return Me
   End Function

   Public Overrides Function toString() As String
      Return String.Format("{0},{1}", _x, _y)
   End Function
End Class

Public Class HomeLocation
   Inherits Location

   Public Overrides Function toString() As String
      Return String.Format("Home Is at: {0},{1}", X(), Y())
   End Function
End Class

Y diga que no tiene acceso a la clase base, o diga que los valores predeterminados son dinámicos, basados ​​en el tiempo, etc. los valores de un método:

  Dim loc As New HomeLocation()
  loc.X(1337)
  PrintLocation(loc)

Pero, ¿no es esto mucho más fácil de leer?

  PrintLocation(New HomeLocation().X(1337))

O, ¿qué pasa con un miembro de la clase?

Public Class Dummy
   Private _locA As New Location()
   Public Sub New()
      _locA.X(1337)
   End Sub
End Class

vs

Public Class Dummy
   Private _locC As Location = New Location().X(1337)
End Class

Así es como he estado usando el encadenamiento, y típicamente mis métodos son solo para la configuración, por lo que solo tienen 2 líneas de largo, establezca un valor, entonces Return Me. Para nosotros, ha limpiado grandes líneas muy difíciles de leer y entender el código en una línea que se lee como una oración. algo como

New Dealer.CarPicker().Subaru.WRX.SixSpeed.TurboCharged.BlueExterior.GrayInterior.Leather.HeatedSeats

Vs algo como

New Dealer.CarPicker(Dealer.CarPicker.Makes.Subaru
                   , Dealer.CarPicker.Models.WRX
                   , Dealer.CarPicker.Transmissions.SixSpeed
                   , Dealer.CarPicker.Engine.Options.TurboCharged
                   , Dealer.CarPicker.Exterior.Color.Blue
                   , Dealer.CarPicker.Interior.Color.Gray
                   , Dealer.CarPicker.Interior.Options.Leather
                   , Dealer.CarPicker.Interior.Seats.Heated)

Detrimento del encadenamiento
, es decir, donde no me gusta usarlo

No uso el encadenamiento cuando hay muchos parámetros para pasar a las rutinas, principalmente porque las líneas se alargan mucho y, como mencionó el OP, puede ser confuso cuando se llaman rutinas a otras clases para pasar a una de las Los métodos de encadenamiento.

También existe la preocupación de que una rutina devuelva datos no válidos, hasta ahora solo he usado encadenamiento cuando devuelvo la misma instancia que se llama. Como se señaló si encadena entre clases, dificulta la depuración (¿cuál devuelve nulo?) Y puede aumentar el acoplamiento de dependencias entre clases.

Conclusión

Como todo en la vida y la programación, encadenar no es bueno ni malo si puedes evitar lo malo, entonces encadenar puede ser un gran beneficio.

Intento seguir estas reglas.

  1. Intenta no encadenar entre clases
  2. Hacer rutinas específicamente para encadenar
  3. Haz solo UNA cosa en una rutina de encadenamiento
  4. Úselo cuando mejore la legibilidad
  5. Úselo cuando simplifica el código

6

El encadenamiento de métodos puede permitir diseñar DSL avanzadas en Java directamente. En esencia, puede modelar al menos estos tipos de reglas DSL:

1. SINGLE-WORD
2. PARAMETERISED-WORD parameter
3. WORD1 [ OPTIONAL-WORD]
4. WORD2 { WORD-CHOICE-A | WORD-CHOICE-B }
5. WORD3 [ , WORD3 ... ]

Estas reglas pueden implementarse usando estas interfaces

// Initial interface, entry point of the DSL
interface Start {
  End singleWord();
  End parameterisedWord(String parameter);
  Intermediate1 word1();
  Intermediate2 word2();
  Intermediate3 word3();
}

// Terminating interface, might also contain methods like execute();
interface End {}

// Intermediate DSL "step" extending the interface that is returned
// by optionalWord(), to make that method "optional"
interface Intermediate1 extends End {
  End optionalWord();
}

// Intermediate DSL "step" providing several choices (similar to Start)
interface Intermediate2 {
  End wordChoiceA();
  End wordChoiceB();
}

// Intermediate interface returning itself on word3(), in order to allow for
// repetitions. Repetitions can be ended any time because this interface
// extends End
interface Intermediate3 extends End {
  Intermediate3 word3();
}

Con estas reglas simples, puede implementar DSL complejos como SQL directamente en Java, como lo hace jOOQ , una biblioteca que he creado. Vea un ejemplo de SQL bastante complejo tomado de mi blog aquí:

create().select(
    r1.ROUTINE_NAME,
    r1.SPECIFIC_NAME,
    decode()
        .when(exists(create()
            .selectOne()
            .from(PARAMETERS)
            .where(PARAMETERS.SPECIFIC_SCHEMA.equal(r1.SPECIFIC_SCHEMA))
            .and(PARAMETERS.SPECIFIC_NAME.equal(r1.SPECIFIC_NAME))
            .and(upper(PARAMETERS.PARAMETER_MODE).notEqual("IN"))),
                val("void"))
        .otherwise(r1.DATA_TYPE).as("data_type"),
    r1.NUMERIC_PRECISION,
    r1.NUMERIC_SCALE,
    r1.TYPE_UDT_NAME,
    decode().when(
    exists(
        create().selectOne()
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.notEqual(r1.SPECIFIC_NAME))),
        create().select(count())
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.lessOrEqual(r1.SPECIFIC_NAME)).asField())
    .as("overload"))
.from(r1)
.where(r1.ROUTINE_SCHEMA.equal(getSchemaName()))
.orderBy(r1.ROUTINE_NAME.asc())
.fetch()

Otro buen ejemplo es jRTF , un pequeño DSL diseñado para cementar documentos RTF directamente en Java. Un ejemplo:

rtf()
  .header(
    color( 0xff, 0, 0 ).at( 0 ),
    color( 0, 0xff, 0 ).at( 1 ),
    color( 0, 0, 0xff ).at( 2 ),
    font( "Calibri" ).at( 0 ) )
  .section(
        p( font( 1, "Second paragraph" ) ),
        p( color( 1, "green" ) )
  )
).out( out );

@ user877329: Sí, se puede usar en casi cualquier lenguaje de programación orientado a objetos que sepa algo como interfaces y polimorfismo de subtipo
Lukas Eder

4

El encadenamiento de métodos puede ser simplemente una novedad para la mayoría de los casos, pero creo que tiene su lugar. Se puede encontrar un ejemplo en el uso del registro activo de CodeIgniter :

$this->db->select('something')->from('table')->where('id', $id);

Eso se ve mucho más limpio (y tiene más sentido, en mi opinión) que:

$this->db->select('something');
$this->db->from('table');
$this->db->where('id', $id);

Realmente es subjetivo; Cada uno tiene su propia opinión.


Este es un ejemplo de una interfaz fluida con el método de encadenamiento, por lo que UseCase es ligeramente diferente aquí. No solo estás encadenando, sino que estás creando un lenguaje específico de dominio interno que se lee fácilmente. En una nota al margen, el ActiveRecord de CI no es un ActiveRecord.
Gordon

3

Creo que la falacia principal es pensar que este es un enfoque orientado a objetos en general cuando, de hecho, es más un enfoque de programación funcional que cualquier otra cosa.

Las principales razones por las que lo uso son para la legibilidad y para evitar que mi código se vea inundado por variables.

Realmente no entiendo de qué están hablando los demás cuando dicen que daña la legibilidad. Es una de las formas de programación más concisas y coherentes que he usado.

También esto:

convertTextToVoice.LoadText ("source.txt"). ConvertToVoice ("destination.wav");

así es como lo usaría típicamente. Usarlo para encadenar x número de parámetros no es como lo uso típicamente. Si quisiera poner x número de parámetros en una llamada al método, usaría la sintaxis de params :

public void foo (params object [] items)

Y convierta los objetos según el tipo o simplemente use una matriz o colección de tipos de datos según su caso de uso.


1
+1 sobre "la falacia principal es pensar que este es un enfoque orientado a objetos en general cuando en realidad es más un enfoque de programación funcional que cualquier otra cosa". El caso de uso prominente es para manipulaciones sin estado en objetos (donde en lugar de cambiar su estado, devuelve un nuevo objeto en el que continúa actuando). Las preguntas del OP y otras respuestas muestran acciones con estado que de hecho parecen incómodas con el encadenamiento.
OmerB

Sí, tiene razón, es una manipulación sin estado, excepto que normalmente no creo un nuevo objeto, sino que uso la inyección de dependencia para que sea un servicio disponible. Y sí, los casos de uso con estado no son para lo que creo que el encadenamiento de métodos estaba destinado. La única excepción que veo es si inicializa el servicio DI con algunas configuraciones y tiene algún tipo de perro guardián para monitorear el estado, como un servicio COM de algún tipo. Solo en mi humilde opinión.
Shane Thorndike

2

Estoy de acuerdo, por lo tanto, cambié la forma en que se implementó una interfaz fluida en mi biblioteca.

Antes de:

collection.orderBy("column").limit(10);

Después:

collection = collection.orderBy("column").limit(10);

En la implementación "antes", las funciones modificaron el objeto y terminaron en return this. Cambié la implementación para devolver un nuevo objeto del mismo tipo .

Mi razonamiento para este cambio :

  1. El valor de retorno no tenía nada que ver con la función, estaba simplemente allí para soportar la parte de encadenamiento. Debería haber sido una función nula según OOP.

  2. El encadenamiento de métodos en las bibliotecas del sistema también lo implementa de esa manera (como linq o string):

    myText = myText.trim().toUpperCase();
    
  3. El objeto original permanece intacto, lo que permite al usuario de la API decidir qué hacer con él. Permite:

    page1 = collection.limit(10);
    page2 = collection.offset(10).limit(10);
    
  4. Una implementación de copia también se puede utilizar para construir objetos:

    painting = canvas.withBackground('white').withPenSize(10);
    

    Donde la setBackground(color)función cambia la instancia y no devuelve nada (como se supone que debe hacerlo) .

  5. El comportamiento de las funciones es más predecible (ver puntos 1 y 2).

  6. El uso de un nombre de variable corto también puede reducir el desorden de código, sin forzar una API en el modelo.

    var p = participant; // create a reference
    p.addSchedule(events[1]);p.addSchedule(events[2]);p.setStatus('attending');p.save()
    

Conclusión:
en mi opinión, una interfaz fluida que utiliza una return thisimplementación es simplemente incorrecta.


1
¿Pero no devolver una nueva instancia para cada llamada generaría una sobrecarga, especialmente si está utilizando elementos más grandes? Al menos para idiomas no gestionados.
Apeiron

@Apeiron Performance-wise definitivamente es más rápido de return thisadministrar o no. Es mi opinión que obtiene una API fluida de una manera "no natural". (Se agregó la razón 6: para mostrar una alternativa no fluida, que no tiene la funcionalidad de sobrecarga / agregada)
Bob Fanger

Estoy de acuerdo contigo. En su mayoría, es mejor dejar intacto el estado subyacente de un DSL y devolver nuevos objetos en cada llamada a un método, en cambio ... Tengo curiosidad: ¿Cuál es la biblioteca que has mencionado?
Lukas Eder

1

El punto totalmente perdido aquí es que el método de encadenamiento permite DRY . Es un sustituto efectivo para el "con" (que está mal implementado en algunos idiomas).

A.method1().method2().method3(); // one A

A.method1();
A.method2();
A.method3(); // repeating A 3 times

Esto importa por la misma razón que DRY siempre importa; Si A resulta ser un error y estas operaciones deben realizarse en B, solo necesita actualizar en 1 lugar, no en 3.

Pragmáticamente, la ventaja es pequeña en este caso. Aún así, un poco menos escribiendo, un poco más robusto (SECO), lo tomaré.


14
Repetir un nombre de variable en el código fuente no tiene nada que ver con el principio DRY. DRY declara que "Todo conocimiento debe tener una representación única, inequívoca y autorizada dentro de un sistema", o en otros términos, evitar la repetición del conocimiento (no la repetición del texto ).
pedromanoel

44
Sin duda, es la repetición lo que viola la sequedad. La repetición de nombres de variables (innecesariamente) causa todo el mal de la misma manera que otras formas de sequedad: crea más dependencias y más trabajo. En el ejemplo anterior, si cambiamos el nombre de A, la versión húmeda necesitará 3 cambios y provocará errores para depurar si se pierde alguno de los tres.
Anfurny

1
No puedo ver el problema que señaló en este ejemplo, ya que todas las llamadas a métodos están cercanas entre sí. Además, si olvida cambiar el nombre de una variable en una línea, el compilador devolverá un error y esto se corregirá antes de que pueda ejecutar su programa. Además, el nombre de una variable está limitado al alcance de su declaración. A menos que esta variable sea global, lo que ya es una mala práctica de programación. OMI, SECO no se trata de escribir menos, sino de mantener las cosas aisladas.
pedromanoel

El "compilador" puede devolver un error, o tal vez está utilizando PHP o JS y su intérprete solo puede emitir un error en tiempo de ejecución cuando alcanza esta condición.
Anfurny

1

Generalmente odio el método de encadenamiento porque creo que empeora la legibilidad. La compacidad a menudo se confunde con la legibilidad, pero no son los mismos términos. Si haces todo en una sola declaración, entonces eso es compacto, pero es menos legible (más difícil de seguir) la mayoría de las veces que hacerlo en varias declaraciones. Como notó, a menos que no pueda garantizar que el valor de retorno de los métodos utilizados sea el mismo, el encadenamiento de métodos será una fuente de confusión.

1.)

participant
    .addSchedule(events[1])
    .addSchedule(events[2])
    .setStatus('attending')
    .save();

vs

participant.addSchedule(events[1]);
participant.addSchedule(events[2]);
participant.setStatus('attending');
participant.save()

2.)

participant
    .getSchedule('monday')
        .saveTo('monnday.file');

vs

mondaySchedule = participant.getSchedule('monday');
mondaySchedule.saveTo('monday.file');

3.)

participant
    .attend(event)
    .setNotifications('silent')
    .getSocialStream('twitter')
        .postStatus('Joining '+event.name)
        .follow(event.getSocialId('twitter'));

vs

participant.attend(event);
participant.setNotifications('silent')
twitter = participant.getSocialStream('twitter')
twitter.postStatus('Joining '+event.name)
twitter.follow(event.getSocialId('twitter'));

Como puede ver, gana casi nada, porque debe agregar saltos de línea a su declaración única para que sea más legible y tiene que agregar sangría para dejar en claro que está hablando de diferentes objetos. Bueno, si quisiera usar un lenguaje basado en la ideación, aprendería Python en lugar de hacerlo, sin mencionar que la mayoría de los IDE eliminarán la sangría formateando automáticamente el código.

Creo que el único lugar donde este tipo de encadenamiento puede ser útil es canalizar secuencias en la CLI o unir múltiples consultas juntas en SQL. Ambos tienen un precio por múltiples declaraciones. Pero si desea resolver problemas complejos, terminará incluso con aquellos que pagan el precio y escriben el código en múltiples declaraciones utilizando variables o escribiendo scripts de bash y procedimientos almacenados o vistas.

A partir de las interpretaciones DRY: "Evite la repetición del conocimiento (no la repetición del texto)". y "Escriba menos, ni siquiera repita textos", el primero lo que el principio significa realmente, pero el segundo es un malentendido común porque muchas personas no pueden entender una mierda demasiado complicada como "Todo conocimiento debe tener una sola, inequívoca, representación autorizada dentro de un sistema ". El segundo es la compacidad a toda costa, que se rompe en este escenario, porque empeora la legibilidad. La primera interpretación se divide por DDD cuando copia código entre contextos limitados, porque el acoplamiento flexible es más importante en ese escenario.


0

El bueno:

  1. Es breve, pero le permite poner más en una sola línea con elegancia.
  2. A veces puede evitar el uso de una variable, que en ocasiones puede ser útil.
  3. Puede funcionar mejor.

El malo:

  1. Estás implementando devoluciones, esencialmente agregando funcionalidad a los métodos en objetos que no son realmente parte de lo que esos métodos deben hacer. Está devolviendo algo que ya tiene simplemente para guardar unos pocos bytes.
  2. Oculta los cambios de contexto cuando una cadena conduce a otra. Puede obtener esto con getters, excepto que es bastante claro cuando cambia el contexto.
  3. El encadenamiento sobre varias líneas se ve feo, no juega bien con la sangría y puede causar cierta confusión en el manejo del operador (especialmente en idiomas con ASI).
  4. Si desea comenzar a devolver algo más que sea útil para un método encadenado, posiblemente tendrá más dificultades para solucionarlo o tendrá más problemas con él.
  5. Estás descargando el control a una entidad a la que normalmente no descargarías simplemente por comodidad, incluso en idiomas estrictamente escritos, los errores causados ​​por esto no siempre se pueden detectar.
  6. Puede funcionar peor.

General:

Un buen enfoque es no utilizar el encadenamiento en general hasta que surjan situaciones o módulos específicos serían particularmente adecuados para ello.

El encadenamiento puede dañar gravemente la legibilidad en algunos casos, especialmente cuando se pesan en los puntos 1 y 2.

En caso de acusaciones, se puede utilizar de forma incorrecta, como en lugar de otro enfoque (pasar una matriz, por ejemplo) o mezclar métodos de formas extrañas (parent.setSomething (). GetChild (). SetSomething (). GetParent (). SetSomething ()).


0

Respuesta Opinión

El mayor inconveniente del encadenamiento es que puede ser difícil para el lector comprender cómo cada método afecta al objeto original, si lo hace, y qué tipo devuelve cada método.

Algunas preguntas:

  • ¿Los métodos en la cadena devuelven un nuevo objeto, o el mismo objeto mutado?
  • ¿Todos los métodos en la cadena devuelven el mismo tipo?
  • Si no, ¿cómo se indica cuando cambia un tipo en la cadena?
  • ¿Se puede descartar con seguridad el valor devuelto por el último método?

La depuración, en la mayoría de los idiomas, puede ser más difícil de encadenar. Incluso si cada paso de la cadena está en su propia línea (lo que de alguna manera frustra el propósito del encadenamiento), puede ser difícil inspeccionar el valor devuelto después de cada paso, especialmente para los métodos no mutantes.

Los tiempos de compilación pueden ser más lentos según el idioma y el compilador, ya que las expresiones pueden ser mucho más complejas de resolver.

Creo que, como con todo, encadenar es una buena solución que puede ser útil en algún escenario. Debe usarse con precaución, comprender las implicaciones y limitar el número de elementos de la cadena a unos pocos.


0

En los idiomas escritos (que carecen autoo equivalentes) esto evita que el implementador tenga que declarar los tipos de resultados intermedios.

import Participant
import Schedule

Participant participant = new Participant()
... snip...
Schedule s = participant.getSchedule(blah)
s.saveTo(filename)

Para cadenas más largas, puede estar tratando con varios tipos intermedios diferentes, necesitaría declarar cada uno de ellos.

Creo que este enfoque realmente se desarrolló en Java, donde a) todas las llamadas a funciones son invocaciones de funciones miembro, yb) se requieren tipos explícitos. Por supuesto, hay una compensación aquí en términos, perdiendo algo de claridad, pero en algunas situaciones algunas personas encuentran que vale la pena.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.