Encadenar llamadas a funciones. Lo que necesitas saber sobre la Ley de Demeter
Hace tiempo, cuatro años y pico, le dediqué una entrada del blog a esta famosa ley del desarrollo de software. En aquella ocasión, tras enterarme de su existencia leyendo el libro Clean Code de Robert C. Martin y leer algunos post en internet sobre ella, pensé que la había comprendido y entonces cada vez que he tenido oportunidad de ir aplicándola lo he hecho, o eso pensaba. Pero, tras la lectura del libro Elegant Objects en el que hay un capítulo referente a esta Ley, me puso en aviso de que quizás no la había entendido correctamente así que nada mejor que repasar, leer y escribir sobre ella detenidamente.
De manera resumida, aunque luego hablaré más en profundidad de ella, dice lo siguiente (copio del post anterior que escribí):
Esta ley indica como un método f de una clase C debe comunicarse con otros métodos. De tal forma, dicho método f debe invocar métodos de:
C
Un objeto creado por f
Un objeto pasado como argumento a f
Un objeto en una variable de instancia de C
Es verdad que esta ley a priori parece clara, concisa y no deja lugar a dudas. Básicamente uno puede entenderla como que no es correcto hacer el típico encadenamiento de llamadas: book()->pages()->first()->text() pero, ¿es así?, ¿realmente nos está diciendo que no encadenemos nunca ningún método? ¿existe alguna regla que permita el encadenamiento de llamadas a métodos?
Lo que de verdad dice la Ley de Demeter
Esta ley aparece en un artículo de K.Lieberherr, I.Holland, y A.Riel llamado Object-Oriented Programming: An Objective Sense of Style escrito en el año 1988.
La definición de la Ley que aparece en el documento es la siguiente:
Básicamente dice lo que he comentado anteriormente, es decir, que un método M de una clase C, solo puede enviar mensajes a ‘cosas’ de M y cosas de C. Pero, además, y aquí llega lo interesante y el motivo de este post, a la definición anterior le sigue el párrafo entre paréntesis que casi nunca he visto reflejado en ningún sitio excepto en el libro Elegant Objects. Ojo a lo que dice, léelo despacio y entiéndelo porque es importante:
Que viene a decir que también son válidos…
Los objetos creados por M o por funciones o métodos a los que llama M y objetos en variables globales son considerados argumentos de M.
Es decir, que la Ley de Demeter nos permite ‘enviar un mensaje’ (llamar a un método) a todos los objetos creados en las llamadas de M, ya sea de forma directa o indirecta, de manera que nuestras llamadas siempre se hagan sobre métodos ‘constructores’ y no sobre métodos que devuelvan objetos (getters) ya instanciados.
Dicho lo anterior, podemos pensar que los objetos no permitidos son aquellos objetos que ya existían cuando las llamadas a M comenzaron, lo que significa que lo que la Ley de Demeter prohíbe es ‘enviar un mensaje’ (llamar a un método) a cualquier objeto existente (antes de la llamada a M) que esté almacenado en una variable de instancia (atributo) de otro objeto, a no ser que como ya hemos dicho anteriormente, el objeto esté en nuestra clase o nos lo pasen por parámetro.
En resumen, lo que la Ley de Demeter prohíbe es el anidamiento de llamadas a métodos que no instancian objetos pero si permite La Ley de Demeter el anidamiento de llamadas a métodos que instancian y devuelven objetos.
Encadenar llamadas a métodos: “chain calls”
La mayoría de las explicaciones de la Ley de Demeter se centran en los llamados ‘chain calls’ o dicho de otra forma ‘muchos puntos’
home.getOwner().getAddress().getNumber();
Esto, a simple vista es una clara violación de la Ley de Demeter ya que lo más probable es que los valores que estamos obteniendo en esas llamadas ya existieran previamente al momento en el que accedemos a ellos.
Es decir, no es el mero hecho de encadenar llamadas lo que rompe la Ley de Demeter, sino el acceso a ciertos objetos ya existentes antes de la llamada.
Por ejemplo, el siguiente código refactoriza el encadenamiento pero sigue violando la Ley de Demeter, ya que igual que antes estamos accediendo a objetos que ya existían antes de las llamadas.
Owner owner = home.getOwner();
Address ownerAddress = owner.getAddress();
Number ownerNumber = ownerAddress.getNumber();
En cambio, como comenté anteriormente, si estaría permitido encadenar llamadas a funciones siempre que estas fueran llamadas a funciones constructoras, es decir, que instanciaran objetos que no existieran antes de la llamada.
API’s fluídas (Fluent APIs)
Hay quién interpreta que si la Ley de Demeter argumenta que los encadenamientos de llamadas están prohibidos las construcciones de objetos ‘fluent’ también deberían estar prohibidas.
Report report = new ReportBuilder().withBorder(1).withBorderColor(Color.black).withMargin(3).withTitle("Law of Demeter Report").build();
Para determinar si son correctas o no, simplemente podríamos ir analizando cada llamada y ver si cumple con la Ley de Demeter. Tenemos que ReportBuilder es un objeto instanciado en ese momento, por lo que withBorder(1) es un llamada sobre un objeto recién creado y es esto mismo lo que hace esta llamada válida para cumplir la Ley. El resto de llamadas, incluídas la última, son llamadas realizadas (teniendo en cuenta la convención de funcionamiento de las Fluent API’s) sobre el mismo objeto una y otra vez. Como el objeto ha sido creado en este mismo código, todas las llamadas también son válidas.
Por lo tanto, la Ley de Demeter no prohíbe las fluent APIs.
Getters are evil
La mayoría de los ejemplos de violación de la Ley de Demeter viene por usar métodos getter. Normalmente un método getter sirve para acceder al estado de un objeto, es decir, al valor que contenga un atributo de dicho objeto. Un getter no es un método que sirva para crear algo, no crea un objeto, lo devuelve y, como hemos dicho anteriormente, la Ley de Demeter no permite anidar llamadas a métodos que no instancien objetos.
El objeto que contenta el atributo al que se accede por el getter, lógicamente, ha tenido que ser creado antes de la llamada a su método getter correspondiente. Así que, insistiendo en esto, se está violando la Ley de Demeter porque estamos accediendo a objetos creados antes de la llamada a los métodos.
La Ley de Demeter está contra el acceso directo a los atributos de un objeto.
Cuando aplicar la Ley de Demeter
Existen opiniones en algunos artículos que indica que la Ley de Demeter no es tanto una ley sino una recomendación, sugerencia o guía. Que sirve para la teoría pero que en la práctica es muy complicado llevarla a cabo y que no aplica para ciertos objetos como estructuras de datos, entidades, value objects, dto’s...etc.
Pero, un programador que desarrolle bajo el paradigma de la orientación a objetos, tendría que tener muy en cuenta esta Ley. Como se indica en el paper original, es la “Ley del Buen Estilo” y si cumplimos con ella tanto en las formas (parte técnica) como en el fondo (ideas detrás de la Ley) conseguiremos escribir un código orientado a objetos adecuado y bien diseñado fomentando la encapsulación de los objetos y ocultando su estado interno.
Para una total comprensión de la Ley de Demeter, mi recomendación es leer el paper original en el que se presentó la Ley de Demeter y que está enlazado más abajo en la sección de bibliografía.
Esto es todo. Como siempre, cualquier comentario, duda o sugerencia es bienvenida.
¡Chimpún!
Bibliografía
Paper original: K. Lieberherr, I. IIolland, A. Riel — OOPSLA ’88 Proceedin: https://www2.ccs.neu.edu/research/demeter/papers/law-of-demeter/oopsla88-law-of-demeter.pdf