Existe el clásico problema OOP de encadenamiento de métodos versus métodos de "punto de acceso único":
main.getA().getB().getC().transmogrify(x, y)
vs
main.getA().transmogrifyMyC(x, y)
La primera parece tener la ventaja de que cada clase solo es responsable de un conjunto más pequeño de operaciones, y hace que todo sea mucho más modular : agregar un método a C no requiere ningún esfuerzo en A, B o C para exponerlo.
La desventaja, por supuesto, es una encapsulación más débil , que resuelve el segundo código. Ahora A tiene el control de cada método que lo atraviesa y puede delegarlo en sus campos si así lo desea.
Me doy cuenta de que no hay una solución única y, por supuesto, depende del contexto, pero realmente me gustaría escuchar algunos comentarios sobre otras diferencias importantes entre los dos estilos, y en qué circunstancias debería preferir cualquiera de ellos, porque ahora, cuando lo intento para diseñar un código, siento que simplemente no estoy usando los argumentos para decidir de una forma u otra.
En general, trato de mantener el método de encadenamiento lo más limitado posible (basado en la Ley de Demeter )
La única excepción que hago es para interfaces fluidas / programación interna de estilo DSL.
Martin Fowler hace una especie de distinción en los lenguajes específicos del dominio, pero por razones de violación de la separación de consultas de comandos que establece:
Fowler en su libro en la página 70 dice:
fuente
Creo que la pregunta es si estás usando una abstracción adecuada.
En el primer caso, tenemos
Donde main es de tipo
IHasGetA
. La pregunta es: ¿es adecuada esa abstracción? La respuesta no es trivial. Y en este caso parece un poco extraño, pero de todos modos es un ejemplo teórico. Pero para construir un ejemplo diferente:A menudo es mejor que
Debido a que en el último ejemplo ambos
this
ymain
dependerá de los tipos dev
,w
,x
,y
yz
. Además, el código no se ve mucho mejor, si cada declaración de método tiene media docena de argumentos.Un localizador de servicios realmente requiere el primer enfoque. No desea acceder a la instancia que crea a través del localizador de servicios.
Por lo tanto, "alcanzar" a través de un objeto puede crear mucha dependencia, más aún si se basa en propiedades de clases reales.
Sin embargo, crear una abstracción, que consiste en proporcionar un objeto, es algo completamente diferente.
Por ejemplo, podrías tener:
¿Dónde
main
está una instancia deMain
. Si el conocimiento de la clasemain
reduce la dependencia a, enIHasGetA
lugar deMain
, encontrará que el acoplamiento es bastante bajo. El código de llamada ni siquiera sabe que en realidad está llamando al último método en el objeto original, que en realidad ilustra el grado de desacoplamiento.Llegas a lo largo de un camino de abstracciones concisas y ortogonales, en lugar de profundizar en lo interno de una implementación.
fuente
La Ley de Deméter , como señala @ Péter Török, sugiere la forma "compacta".
Además, cuantos más métodos mencione explícitamente en su código, más clases dependerá de su clase, lo que aumentará los problemas de mantenimiento. En su ejemplo, la forma compacta depende de dos clases, mientras que la forma más larga depende de cuatro clases. La forma más larga no solo contraviene la Ley de Deméter; también le hará cambiar su código cada vez que cambie cualquiera de los cuatro métodos a los que se hace referencia (a diferencia de los dos en forma compacta).
fuente
A
explotará y muchos métodosA
pueden querer delegar de todos modos. Aún así, estoy de acuerdo con las dependencias, eso reduce drásticamente la cantidad de dependencias requeridas del código del cliente.He luchado con este problema yo mismo. La desventaja de "alcanzar" profundamente en diferentes objetos es que cuando realizas la refactorización terminarás teniendo que cambiar una gran cantidad de código ya que hay tantas dependencias. Además, su código se vuelve un poco hinchado y más difícil de leer.
Por otro lado, tener clases que simplemente "transmiten" métodos también significa una sobrecarga de tener que declarar múltiples métodos en más de un lugar.
Una solución que mitiga esto y es apropiada en algunos casos es tener una clase de fábrica que construya una especie de objeto de fachada copiando datos / objetos de las clases apropiadas. De esa manera, puede codificar contra su objeto de fachada y cuando refactoriza simplemente cambia la lógica de la fábrica.
fuente
A menudo encuentro que la lógica de un programa es más fácil de asimilar con métodos encadenados. Para mí, se
customer.getLastInvoice().itemCount()
adapta mejor a mi cerebro quecustomer.countLastInvoiceItems()
.Depende de usted si vale la pena el dolor de cabeza de mantenimiento de tener el acoplamiento adicional. (También me gustan las funciones pequeñas en clases pequeñas, así que tiendo a encadenar. No digo que sea correcto, es solo lo que hago).
fuente