¿Esto viola la Ley de Demeter?

7

Supongamos que tengo una clase SelectableEntity<T extends Entity> que tiene tres métodos, select , deselect , isSelected y count .

Para tomar un ejemplo un tanto artificial, digamos que estoy creando una aplicación de mensajería de emergencia que me permite enviar mensajes a los residentes durante incendios, inundaciones y otros desastres naturales.

Puedo seleccionar varias entidades de diferentes tipos (pero todas por la misma razón). Digamos que estas entidades son House , Street y Suburb .

Ahora digamos que tengo una clase llamada MessageRecipients que contiene SelectableEntity<House> , SelectableEntity<Street> , SelectableEntity<Suburb> .

Hay dos maneras de codificar esto:

La primera forma es crear captadores (es decir, houses , streets , suburbs ) y seleccionar / deseleccionar los destinatarios de esa manera .: messageRecipients.streets.select(aStreet) .

La otra forma sería ocultar el hecho de que MessageRecipients está usando SelectableEntity y simplemente crear métodos 'proxy' para cada método en SelectableEntity (es decir, selectHouse , deselectHouse , selectStreet , etc).

El primer método parece que viola la Ley de Deméter, mientras que el segundo método básicamente me obliga a crear un montón de métodos en MessageRecipients que invocan directamente la instancia relevante de SelectableEntity (y requieren un montón de pruebas adicionales para garantizar que se invocan los métodos correctos).

Mi pregunta es si el primer ejemplo es un ejemplo estándar de una violación de la Ley de Demeter y preferiría soportar toda la duplicación y verbosidad e ir por el segundo enfoque (supongo que seguir una pregunta es lo que constituye un válido ¿Razón para violar la Ley de Deméter, si es que hay alguna?

Nota: el ejemplo que he dado está confeccionado y soy consciente de que podría descomponerse en ciertas situaciones (es decir, las calles y los suburbios contienen casas; si agrego una calle que tiene la casa XYZ y llamo messageRecipients.houses().isSelect(houseXYZ) debería devolver verdadero). Por el bien de esta discusión, ese caso debe ignorarse, ya que no se aplica al escenario real con el que estoy tratando.

    
pregunta NRaf 04.03.2018 - 05:20

3 respuestas

2
  

Hay dos maneras de codificar esto:

     

La primera forma es crear captadores (es decir, casas, calles, suburbios) y seleccionar / deseleccionar a los destinatarios de esa manera .: messageRecipients.streets.select (aStreet).

No creo que prefiera de esta manera, pero no quiero que la Ley de Demeter se aplique incorrectamente.

Algunos piensan que esto es todo lo que LoD tiene que decir:

  

la Ley de Demeter para funciones requiere que un método m de un objeto O solo pueda invocar los métodos de los siguientes tipos de objetos:

     
  • O mismo
  •   
  • parámetros de m
  •   
  • Cualquier objeto creado / creado dentro de m
  •   
  • objetos componentes directos de O
  •   
  • Una variable global, accesible por O, en el alcance de m
  •   

En particular, un objeto debería evitar invocar métodos de un objeto miembro devuelto por otro método . Para muchos lenguajes modernos orientados a objetos que usan un punto como identificador de campo, la ley se puede establecer simplemente como "usar solo un punto". Es decir, el código a.b.Method () infringe la ley donde a.Method () no lo hace. Como una analogía, cuando uno quiere que un perro camine, no le ordena a las piernas del perro que caminen directamente; en cambio, uno le ordena al perro que luego controla sus propias piernas.

     

Wikipedia: Law of Demeter

Este es un monumento al pensamiento estructural. No hay un solo pensamiento semántico o conceptual en nada de eso. Un analizador estático podría hacer cumplir esto. Bleh No es todo en lo que pienso cuando pienso en el LoD .

La Ley de Demeter no es un conteo de puntos ejercicio.

Cuando eres un objeto, es mejor hablar solo con tus amigos, no con amigos de amigos. Si extrae aleatoriamente cualquier cosa que necesite, pronto descubrirá que ha reunido tantas cosas que cualquier cambio en el código base le hará daño aquí.

Los amigos son cosas cuyas interfaces posees. Solo entonces se te promete que puedes saltar a través de estos puntos y siempre encontrar lo mismo. Si bien eso puede suceder, no va a ser cierto si simplemente tomas al azar lo que quieras de una base de código.

Entonces, si el cliente que llama "messageRecipients.streets.select (aStreet)" posee la interfaz para messageRecipients y streets , entonces está hablando con sus amigos y no hay una violación de LoD. Esto significa que el cliente posee todas las interfaces que utiliza. No deben cambiar sin el permiso del cliente. Esto debe quedar claro para los programadores de mantenimiento. Esto necesita límites claros. Pero no se trata de contar puntos.

Ese es el verdadero problema. Debido a la forma en que obtiene este otro objeto, la dependencia de su interfaz no es explícita. La dependencia puede ser una sorpresa. Si respetas eso, encontrarás una manera de dejar clara esta dependencia a otros codificadores.

Esto no debería ser una situación improvisada. Esta debe ser una situación creada por un diseño cuidadoso. Lo que se está creando aquí en realidad tiene un nombre. Se denomina DSL . Si eso es lo que estás haciendo bien. Si esto es solo una cosa que se lanza al azar porque la necesitas, entonces estás creando un problema esperando a que suceda.

El otro nombre por el que pasa el LoD es "el principio de conocimiento mínimo". Le está pidiendo que comprenda que cuando crea estas cadenas de puntos, está agregando conocimiento a su cliente, no solo de estas interfaces, sino también de cómo se conectan. Cuanto más sabes, más duele cuando las cosas cambian. No cree cadenas que nadie prometió que serían estables.

  

La otra forma sería ocultar el hecho de que MessageRecipients está usando SelectableEntity y simplemente cree métodos 'proxy' para cada método en SelectableEntity (es decir, selectHouse, deselectHouse, selectStreet, etc.).

Me gusta esta idea, pero también me gustaría ocultar si lo que se está seleccionando es una casa, una calle o un suburbio. Deja que el suburbio sea lo único que importa que sea un suburbio.

    
respondido por el candied_orange 04.03.2018 - 11:47
10

Ninguna de las soluciones propuestas cumple con los requisitos que mencionaste, y sospecho que es por eso que no se ajusta (o bien viola LoD o necesita métodos de proxy para las cosas).

De lo que describiste, permíteme proponer un diseño diferente. Como entiendo MessageRecipients son Residents . Así que olvidemos el término técnico y mantengámonos con Residents . El punto de Residents es que pueden ser alerted . Así que vamos a escribir eso:

public interface Residents {
    void alert(Message msg);
}

Ahora como lo entiendo, hay diferentes tipos de "ubicaciones" que contienen residentes. Básicamente, estas cosas representan un conjunto de residentes que pueden ser alertados juntos. Así que estos son un tipo especial de "residentes" para los propósitos de nuestra aplicación .

public final class House implements Residents {
    ...
    public House(...address, etc?...) {
        ...
    }

    @Override
    public void alert(Message msg) {
        ...
    }
}

Además, es posible que desee alertar a House , Suburb y Street residentes juntos. En este caso, necesita una implementación "agregada" como esta:

public final class AggregateResidents implements Residents {
    private final List<Residents> residents;

    public AggregateResidents(List<Residents> residents) {
        this.residents = residents;
    }

    @Override
    public void alert(Message msg) {
        residents.forEach(r -> r.alert(msg));
    }
}

Eso es todo. Puede conectar estas cosas de la forma que desee, y puede alertar a cualquier combinación de residentes sin violar el LoD, ni necesitar métodos proxy en cualquier lugar.

Lo sé, probablemente tenga otros requisitos que podrían invalidar este diseño. Lo que estoy señalando es que intente alejarse de un diseño técnico, alejarse del modelado de datos y tratar de concentrarse en los conceptos no técnicos. Eso a veces es realmente difícil, pero automáticamente se hará cargo de todas las leyes y reglas.

    
respondido por el Robert Bräutigam 04.03.2018 - 23:06
2

Si te comprendo correctamente, tu Seleccionable es un objeto de colección con sintaxis para marcar algunos de sus elementos como seleccionados.

En sí mismo, esto está bien, pero su uso, exponiendo más de una colección en su objeto MessageRecipients, es malo (tm)

El LoD es un poco vago, pero podemos enumerar las cosas malas acerca de exponer los tres tipos de lista

  1. El código de llamada debe saber qué tipo de cosa está tratando de seleccionar y relacionar con la propiedad apropiada.

  2. Los tipos adicionales que se agreguen más tarde (¿estado?) cambiarán la firma de los Destinatarios de mensajes

Su segunda solución, la adición de métodos, también tiene estos problemas.

Lo ideal es que quieras.

MessageRecipients.Select (destinatario IAlertable)

Dado que el propósito del objeto es enviar alertas, solo nos importa que los destinatarios puedan hacer eso.

Internamente quizás sepamos sobre los tipos y tenemos las tres listas. Pero como es interno, podemos agregarlos más tarde y no afectar el código de llamada.

Nb. Dependiendo de los detalles, puede optar por IS seleccionable o solo por Id de cadena

    
respondido por el Ewan 04.03.2018 - 10:15

Lea otras preguntas en las etiquetas