Según la ley de Demeter, ¿se le permite a una clase devolver a uno de sus miembros?


13

Tengo tres preguntas sobre la ley de Demeter.

Además de las clases que se designaron específicamente para devolver objetos, como las clases de fábrica y de constructor, ¿está bien que un método devuelva un objeto, por ejemplo, un objeto en poder de una de las propiedades de la clase o violaría la ley de demeter (1) ? Y si viola la ley de demeter, ¿importaría si el objeto devuelto es un objeto inmutable que representa un dato y no contiene nada más que captadores para estos datos (2a)? ¿O tal ValueObject es un antipatrón en sí mismo, porque todo lo que se hace con los datos en esa clase se hace fuera de la clase (2b)?

En pseudocódigo:

class A {}

class B {

    private A a;

    public A getA() {
        return this.a;
    }
}

class C {

    private B b;
    private X x;

    public void main() {
        // Is it okay for B.getA to exist?
        A a = this.b.getA();

        a.doSomething();

        x.doSomethingElse(a);
    }
}

Sospecho que la ley de Demeter prohíbe un patrón como el anterior. ¿Qué puedo hacer para garantizar que doSomethingElse()se pueda llamar sin violar la ley (3)?


99
Mucha gente (programadores funcionales en particular) ve la Ley de Demeter como un antipatrón. Otros lo ven como la "Sugerencia ocasionalmente útil de Demeter".
David Hammen

1
Voy a votar esta pregunta porque ilustra los absurdos de la Ley de Deméter. Sí, LoD es "ocasionalmente útil", pero generalmente es estúpido.
user949300

¿Cómo se xestá preparando?
candied_orange

@CandiedOrange xes solo una propiedad diferente cuyo valor es solo un objeto que puede invocar doSomethingElse.
user2180613

1
Su ejemplo no viola LOD. LOD se trata de evitar que los clientes de un objeto se acoplen a los detalles internos o colaboradores de ese objeto. se quiere evitar cosas como user.currentSession.isLoggedIn(). porque une a clientes de userno solo usersino también a su colaborador session,. En cambio, quieres poder escribir user.isLoggedIn(). esto generalmente se logra agregando un isLoggedInmétodo a usercuya implementación delegan currentSession.
Jonás

Respuestas:


7

En muchos lenguajes de programación, todos los valores devueltos son objetos. Como otros han dicho, no poder usar los métodos de los objetos devueltos te obliga a no devolver nada en absoluto. Debería preguntarse: "¿Cuáles son las responsabilidades de las clases A, B y C?" Es por eso que el uso de nombres de variables metasintácticas como A, B y C siempre solicita la respuesta "depende" porque no hay responsabilidades de herencia en esos términos.

Es posible que desee analizar el diseño impulsado por dominio, que le dará un conjunto de heurísticas más matizadas para razonar sobre dónde debería ir la funcionalidad y quién debería invocar qué.

Su segunda pregunta con respecto a los objetos inmutables habla de la noción de un
Objeto de valor en comparación con un objeto Entidad. en DDD, puede pasar objetos de valor sin restricciones. Esto no dice verdad para los objetos de entidad.

El LOD simplista se expresa mucho mejor en DDD como las reglas para acceder a los agregados . Ningún objeto externo debe contener referencias a miembros de agregados. Solo se debe tener una referencia raíz.

Dejando a un lado DDD, al menos desea desarrollar sus propios conjuntos de estereotipos para sus clases que sean razonables para su dominio. Aplique reglas sobre esos estereotipos a medida que diseña su sistema.

También recuerde siempre que todas estas reglas son para gestionar la complejidad, no para tener isquiotibiales.


1
¿DDD proporciona alguna regla sobre cuándo los miembros de la clase, como los datos, pueden estar expuestos (es decir, los captadores deberían existir)? Todas las discusiones en torno a Law of Demeter, tell, don't ask, getters are evil, object encapsulationy single responsibility principleparecen reducirse a esta pregunta: ¿cuándo se debe utilizar delegación en un objeto de dominio en lugar de obtener el valor del objeto y hacer algo con ella? Sería muy útil poder aplicar calificaciones matizadas a situaciones en las que se debe tomar tal decisión.
user2180613

Existen pautas como "los captadores son malvados" porque muchas personas tratan las instancias de clase como estructuras de datos (una bolsa de datos que se debe transmitir). Esta no es una programación orientada a objetos. Los objetos son paquetes de funcionalidad / comportamiento que realmente proporcionan algo de trabajo. El alcance del trabajo está definido por la responsabilidad. Obviamente, este trabajo creará objetos que se devuelven de los métodos, etc. Si su clase realmente está haciendo un trabajo significativo que puede definir claramente más allá de "representar el objeto de datos X con los atributos Y y Z", entonces está en el camino correcto . No me estresaría por eso.
Jeremy

Para ampliar ese punto, cuando usa muchos captadores / buscadores, eso generalmente significa que tiene algún método grande en algún lugar que está orquestando todo, lo que Fowler llama un script de transacción. Antes de comenzar a usar un captador, pregúntese ... ¿por qué solicito estos datos del objeto? ¿Por qué esta funcionalidad no está en un método en el objeto? Si no es responsabilidad de esta clase implementar esta funcionalidad, continúe y use un getter. El libro [Refactorización] ( martinfowler.com/books/refactoring.html ) de Fowler es un libro sobre heurística para tales preguntas.
Jeremy

En su libro Implementing Domain-Driven Design, Vaughn Vernon menciona la Ley de Demeter y Tell, Don't Ask en el capítulo 10, págs. 382. Merecería la pena echarle un vistazo si tiene acceso a ella.
Jeremy

6

Según la ley de Demeter, ¿se le permite a una clase devolver a uno de sus miembros?

Si, ciertamente lo es.

Veamos los puntos principales :

  • Cada unidad debe tener un conocimiento limitado sobre otras unidades: solo unidades "estrechamente" relacionadas con la unidad actual.
  • Cada unidad solo debe hablar con sus amigos; No hables con extraños.
  • Solo habla con tus amigos inmediatos.

Los tres te dejan haciendo una pregunta: ¿Quién es un amigo?

Al decidir qué devolver, la Ley de Demeter o el principio de menor conocimiento (LoD) no dicta que se defienda contra los codificadores que insisten en violarlo. Dicta que no obliga a los codificadores a violarlo.

Confundirlos es exactamente la razón por la que muchos piensan que un setter siempre debe volver vacío. No. Debe permitir una forma de realizar consultas (captadores) que no cambien el estado del sistema. Esa es la separación de consulta de comando básica .

¿Esto significa que eres libre de profundizar en una base de código que encadena lo que quieras? No. Encadena solo lo que estaba destinado a ser encadenado. De lo contrario, la cadena podría cambiar y de repente tus cosas se rompen. Esto es lo que se entiende por amigos.

Se pueden diseñar cadenas largas para. Las interfaces fluidas, iDSL, gran parte de Java8 y el buen StringBuilder están diseñados para permitirte construir cadenas largas. No violan LoD porque todo en la cadena está destinado a trabajar juntos y prometió seguir trabajando juntos. Usted viola el demeter cuando encadena cosas que nunca han oído hablar el uno del otro. Los amigos son aquellos que prometieron mantener su cadena funcionando. Amigos de amigos no lo hicieron.

Además de las clases que se designaron específicamente para devolver objetos, como las clases de fábrica y de constructor, ¿está bien que un método devuelva un objeto, por ejemplo, un objeto en poder de una de las propiedades de la clase o violaría la ley de demeter (1) ?

Esto solo crea una oportunidad para violar el demeter. Esto no es una violación. Esto ni siquiera es necesariamente malo.

Y si viola la ley de demeter, ¿importaría si el objeto devuelto es un objeto inmutable que representa un dato y no contiene nada más que captadores para estos datos (2)?

Inmutable es bueno pero irrelevante aquí. Llegar a las cosas a través de una cadena más larga no lo hace mejor. Lo que lo hace mejor es separar la obtención del uso. Si está utilizando, solicite lo que necesita como parámetro. No vayas a buscarlo profundizando en los captadores de extraños.

En pseudocódigo:

Sospecho que la ley de Demeter prohíbe un patrón como el anterior. ¿Qué puedo hacer para garantizar que se pueda llamar a doSomethingElse () sin violar la ley (3)?

Antes de hablar, x.doSomethingElse(a)entiendo que básicamente has escrito

b.getA().doSomething()

Ahora, LoD no es un ejercicio de conteo de puntos . Pero cuando creas una cadena estás diciendo que sabes cómo obtener un A(mediante el uso B) y sabes cómo usar un A. Ahora bien A, y Bmejor había amigos cercanos, ya que sólo ellos acoplados entre sí.

Si acaba pedido algo a mano que una Acomo cosa que podría utilizar Ay no habría cuidado de donde vino y Bpodría vivir una vida feliz y libre de su obsesión por conseguir Aa partir B.

En cuanto a x.doSomethingElse(a)sin detalles de dónde xvino, LoD no tiene nada que decir al respecto.

LoD puede alentar la separación del uso de la construcción . Pero señalaré que si trata religiosamente cada objeto como no amigable, se quedará atrapado escribiendo código en métodos estáticos. Puedes construir un gráfico de objetos maravillosamente complejo de esta manera, pero eventualmente tienes que llamar a un método para comenzar a funcionar. Simplemente tienes que decidir quiénes son tus amigos. No hay forma de alejarse de eso.

Entonces sí, una clase puede devolver a uno de sus miembros bajo LoD. Cuando lo haga, debe dejar en claro si ese miembro es amigable con su clase porque si no lo es, algún cliente puede intentar acoplarlo a esa clase usándolo para obtenerlo antes de usarlo. Eso es importante porque ahora lo que devuelve siempre debe ser compatible con ese uso.

Hay muchos casos en los que esto simplemente no es una preocupación. Las colecciones pueden ignorar esto simplemente porque están destinados a ser amigos de todo lo que los usa. Del mismo modo, los objetos de valor son amigables con todos. Pero si escribió una utilidad de validación de dirección que exigía a un empleado un objeto del que extrajo una dirección, en lugar de simplemente pedir la dirección, es mejor que espere que el empleado y la dirección provengan de la misma biblioteca.


Las interfaces fluidas no son una excepción a LOD debido a alguna noción de amistad ... En una interfaz fluida, cada uno de los métodos de la cadena se llama sobre el mismo objeto, porque todos regresan a sí mismos. settings.darkTheme().monospaceFont()es solo azúcar sintáctico parasettings.darkTheme(); settings.monospaceFont();
Jonás

3
@Jonah Regresar a ti mismo es la máxima amabilidad. Y no todas las interfaces fluidas lo hacen. Muchos iDSL también son fluidos y devuelven diferentes tipos para cambiar los métodos disponibles en diferentes puntos. Considere jOOQ , constructor de paso , Asistente de generación , y mi propia pesadilla constructor monstruo . Fluido es un estilo. No es un patrón
candied_orange

2

Además de las clases que fueron específicamente designadas para devolver objetos [...]

No entiendo muy bien este argumento. Cualquier objeto puede devolver un objeto como resultado de una invocación de mensaje. Especialmente si piensas en "todo como objeto".

Sin embargo, las clases son los únicos objetos que pueden crear instancias de objetos nuevos de su propia clase enviándoles el mensaje "nuevo" (o a menudo reemplazados por una nueva palabra clave en los idiomas OOP más comunes)


De todos modos, con respecto a su pregunta, sospecho que un objeto devuelva una de sus propiedades para ser utilizado en otro lugar. Podría ser que este objeto esté filtrando información sobre su estado o sus detalles de implementación.

Y no querrá que el objeto C sepa sobre los detalles de implementación de B. Esta es una dependencia no deseada del objeto A desde la perspectiva del objeto C.

Por lo tanto, es posible que desee preguntarse si C, al conocer a A, no sabe demasiado sobre el trabajo que tiene que hacer, independientemente de la LOD.

Hay casos en los que esto puede ser legítimo, por ejemplo, pedirle a un objeto de colección uno de sus elementos es apropiado y no infringe ninguna regla de Demeter. Pedir un objeto Libro por su objeto SQLConnection por otro lado, es arriesgado y debe evitarse.

El LOD existe porque muchos programadores tienden a pedir información a los objetos antes de realizar un trabajo fuera de él. LOD está ahí para recordarnos que a veces (si no siempre) estamos complicando demasiado las cosas al querer que nuestros objetos hagan demasiado. A veces solo tenemos que pedirle a otro objeto que haga el trabajo por nosotros. LOD está ahí para hacernos pensar dos veces sobre dónde ubicamos el comportamiento en nuestros objetos.


0

Además de las clases que se designaron específicamente para devolver objetos, como las clases de fábrica y de construcción, ¿está bien que un método devuelva un objeto?

¿Por qué no lo sería? Parece sugerir que es necesario señalar a sus compañeros programadores que la clase está destinada específicamente a devolver objetos, o que no debe devolver objetos en absoluto. En idiomas donde todo es un objeto, esto limitaría severamente sus opciones, ya que no podría devolver nada en absoluto.


El ejemplo es sobre lo implícito b.getA().doSomething()yx.doSomethingElse(b.getA())
user2180613

A a = b.getA(); a.DoSomething();- De vuelta a un punto.
Robert Harvey

2
@ user2180613 - Si quisiera preguntar b.getA().doSomething()y x.doSomethingElse(b.getA())debería haberlo preguntado explícitamente. Tal como está, su pregunta parece ser sobre this.b.getA().
David Hammen

1
Como Ano es miembro de C, no se creó Cy no se le proporcionó C, parece que el uso Aen C(de cualquier manera) viola LoD. Contar puntos no es de lo que se trata LoD, es solo una herramienta ilustrativa. Es posible convertir Driver().getCar().getSteeringWheel().getFrontWheels().toRight()en declaraciones separadas que cada una contiene un punto, pero la violación de LoD sigue ahí. Leí en muchas publicaciones de este foro que la realización de acciones debería delegarse al objeto que implementa el comportamiento real, es decir tell don't ask. Devolver valores parece estar en conflicto con eso.
user2180613

1
Supongo que eso es cierto, pero no responde la pregunta.
user2180613

0

Depende de lo que estés haciendo. Si expone a un miembro de su clase, cuando no es asunto de nadie que este sea un miembro de su clase, o cuál es el tipo de ese miembro, eso es algo malo. Si luego cambia el tipo de ese miembro, y el tipo de la función que devuelve el miembro, y todos tienen que cambiar su código, eso es malo.

Por otro lado, si la interfaz de su clase indica que proporcionará una instancia de una clase A conocida, está bien. Si tiene suerte y puede hacerlo proporcionando un miembro de su clase, bien por usted. Si cambiaste ese miembro de ser A a ser B, entonces deberías reescribir el método que devuelve una A, obviamente, ahora tendrá que construir uno y llenarlo con los datos disponibles, en lugar de un simple retorno. un miembro. La interfaz de su clase no se modificará.

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.