Rails: Law of Demeter Confusion

13

Estoy leyendo un libro llamado Rails AntiPatterns y hablan sobre el uso de la delegación para evitar infringir la Ley de Demeter. Aquí está su primer ejemplo:

Ellos creen que llamar algo así en el controlador es malo (y estoy de acuerdo)

@street = @invoice.customer.address.street

Su solución propuesta es hacer lo siguiente:

class Customer

    has_one :address
    belongs_to :invoice

    def street
        address.street
    end
end

class Invoice

    has_one :customer

    def customer_street
        customer.street
    end
end

@street = @invoice.customer_street

Están diciendo que como solo usas un punto, no estás violando la Ley de Demeter aquí. Creo que esto es incorrecto, porque todavía está pasando por el cliente para ir a través de la dirección para obtener la calle de la factura. Principalmente obtuve esta idea de una publicación de blog que leí:

enlace

En la publicación del blog, el ejemplo principal es

class Wallet
  attr_accessor :cash
end
class Customer
  has_one :wallet

  # attribute delegation
  def cash
    @wallet.cash
  end
end

class Paperboy
  def collect_money(customer, due_amount)
    if customer.cash < due_ammount
      raise InsufficientFundsError
    else
      customer.cash -= due_amount
      @collected_amount += due_amount
    end
  end
end

La publicación del blog indica que aunque solo hay un punto customer.cash en lugar de customer.wallet.cash , este código aún viola la Ley de Demeter.

  

Ahora, en el método de recogida de dinero de Paperboy, no tenemos dos puntos,   acaba de tener uno en "customer.cash". ¿Ha resuelto esta delegación nuestra   ¿problema? De ningún modo. Si nos fijamos en el comportamiento, un paperboy sigue siendo   llegar directamente a la billetera de un cliente para retirar dinero.

EDIT

Entiendo completamente y acepto que esto sigue siendo una violación y necesito crear un método en Wallet llamado retiro que maneje el pago por mí y que debo llamar a ese método dentro de la clase Customer . Lo que no entiendo es que de acuerdo con este proceso, mi primer ejemplo aún viola la Ley de Demeter porque Invoice todavía está llegando directamente a Customer para salir a la calle.

¿Puede alguien ayudarme a aclarar la confusión? He estado buscando durante los últimos 2 días tratando de que este tema se hunda, pero todavía es confuso.

    
pregunta user2158382 17.10.2013 - 10:47

4 respuestas

24

Tu primer ejemplo no no viola la ley de Demeter. Sí, con el código tal como está, diciendo que @invoice.customer_street sí obtiene el mismo valor que un hipotético @invoice.customer.address.street , pero en cada paso del recorrido, el valor devuelto es decidido por el objeto al que se le preguntó : no es que "el vendedor de papel mete la mano en la billetera del cliente", es que "el vendedor de periódicos le pide dinero al cliente, y el cliente obtiene el dinero de su billetera ".

Cuando dices @invoice.customer.address.street , estás asumiendo el conocimiento del cliente y las direcciones internas - esto es lo malo. Cuando dices @invoice.customer_street , estás preguntando a invoice , "hey, me gustaría la calle del cliente, tú decides cómo lo obtienes ". Luego el cliente le dice a su dirección, "oye, me gustaría tu calle, tú decides cómo lo obtienes ".

El empuje de Demeter es no 'nunca puedes saber valores de los objetos que se encuentran lejos en el gráfico "; en lugar de eso,' tú tú mismo no deben desplazarse a lo largo del gráfico de objetos para obtener valores '.

Estoy de acuerdo en que esto puede parecer una distinción sutil, pero considere esto: en el código compatible con Demeter, ¿cuánto necesita cambiar el código cuando cambia la representación interna de address ? ¿Qué pasa con el código no compatible con Demeter?

    
respondido por el AakashM 17.10.2013 - 11:13
2

El primer ejemplo y el segundo en realidad no son muy iguales. Mientras que el primero habla sobre las reglas generales de "un punto", el segundo habla más sobre otras cosas en el diseño OO, especialmente " Diga, no pregunte "

  

La delegación es una técnica efectiva para evitar violaciones a la Ley de Demeter, pero solo por comportamiento, no por atributos. - Del segundo ejemplo, el blog de Dan

De nuevo, " solo por comportamiento, no por atributos "

Si pides atributos, debes preguntar . "Oye, chico, ¿cuánto dinero tienes en el bolsillo? Muéstrame, evaluaré si puedes pagar esto". Eso está mal, ningún empleado de compras se comportará de esta manera. En su lugar, dirán, "por favor pague"

customer.pay(due_amount)

Será deber del cliente evaluar si debe pagar y si puede pagar. Y la tarea del empleado finaliza después de decirle al cliente que pague.

Entonces, ¿el segundo ejemplo demuestra que el primero es incorrecto?

En mi opinión. No , siempre que:

1. Lo haces con autolimitación.

Si bien puede acceder a todos los atributos del cliente en @invoice por delegación, rara vez lo necesita en casos normales.

Piense en una página que muestre una factura en una aplicación Rails. Habrá una sección en la parte superior para mostrar los detalles del cliente. Entonces, en la plantilla de factura, ¿codificarás así?

#customer-info
  = @invoice.customer_name
  = @invoice.customer_address
  ....

Eso es incorrecto e ineficiente. Un mejor enfoque es

#customer-info
  = render partial: 'invoice_header_customer', 
           locals: {customer: @invoice.customer}

Luego deje que el cliente parcial para procesar todos los atributos que pertenecen al cliente.

Por lo general, no necesitas eso. Pero puede tener una página de lista que muestre todas las facturas recientes; hay un campo de información en cada li que muestra el nombre del cliente. En este caso, necesita el atributo del cliente para mostrar, y es totalmente legítimo codificar la plantilla como

= @invoice.customer_name

2. No hay más acciones dependiendo de esta llamada de método.

En el caso anterior de la página de lista, la factura solicitó el atributo de nombre del cliente, pero su propósito real es " muéstrame tu nombre ", por lo que básicamente sigue siendo un comportamiento pero no atributo . No hay más evaluaciones y acciones basadas en este atributo, por ejemplo, si tu nombre es "Mike", me gustarás y te daré 30 días más de crédito. No, la factura solo dice "muéstrame tu nombre", no más. Así que eso es totalmente aceptable de acuerdo con la regla "Diga no preguntar" del ejemplo 2.

    
respondido por el Billy Chan 17.10.2013 - 11:13
0

Lea más en el segundo artículo y creo que la idea será más clara. La idea es que el cliente ofrezca una capacidad para pagar y ocultar completamente dónde se guarda el caso. ¿Es un campo, un miembro de una billetera o algo más? La persona que llama no sabe, no necesita saber y no cambia si los detalles de la implementación cambian.

class Wallet
  attr_accessor :cash
  def withdraw(amount)
     raise InsufficientFundsError if amount > cash
     cash -= amount
     amount
  end
end
class Customer
  has_one :wallet
  # behavior delegation
  def pay(amount)
    @wallet.withdraw(amount)
  end
end
class Paperboy
  def collect_money(customer, due_amount)
    @collected_amount += customer.pay(due_amount)
  end
end

Entonces, creo que tu segunda referencia es dar una recomendación más útil.

La única idea de "un punto" es un éxito parcial, ya que oculta algunos detalles profundos, pero aún así aumenta el acoplamiento entre componentes separados.

    
respondido por el djna 17.10.2013 - 10:54
0

Suena como Dan dio su ejemplo de este artículo: El Paperboy, La Cartera, y la ley de Demeter

  

Ley de Demeter Un método de un objeto debe invocar solo los métodos de los siguientes tipos de objetos:

     
  1. sí mismo
  2.   
  3. sus parámetros
  4.   
  5. cualquier objeto que crea / crea una instancia
  6.   
  7. sus objetos componentes directos
  8.   

Cuándo y cómo aplicar la ley de Demeter

     

Así que ahora tienes una buena comprensión de la ley y sus beneficios, pero   Todavía no hemos discutido cómo identificar lugares en el código existente donde   podemos aplicarlo (e igual de importante, donde NO aplicarlo ...)

     
  1. Declaraciones 'get' encadenadas : el primer lugar, el más obvio para aplicar la Ley de Demeter, son los lugares de código que han repetido get()   declaraciones,

         

    value = object.getX().getY().getTheValue();

         

    como si cuando nuestra persona canónica para este ejemplo fuera detenida por   El policía, podríamos ver:

         

    license = person.getWallet().getDriversLicense();

  2.   
  3. un montón de objetos 'temporales' : el ejemplo de la licencia anterior no sería mejor si el código fuera similar,

         

    Wallet tempWallet = person.getWallet();    license = tempWallet.getDriversLicense();

         

    es equivalente, pero más difícil de detectar.

  4.   
  5. Importando muchas clases : en el proyecto Java en el que trabajo, tenemos la regla de que solo importamos las clases que realmente usamos; nunca ves   algo como

         

    import java.awt.*;

         

    en nuestro código fuente. Con esta regla en su lugar, no es infrecuente   ver una docena o más de las declaraciones de importación todas provenientes del mismo paquete.   Si esto está sucediendo en su código, podría ser un buen lugar para buscar   Por ejemplos oscurecidos de violaciones. Si necesitas importarlo, estás   acoplado a ello. Si cambia, puede que tenga que hacerlo también. Por explícitamente   importando las clases, comenzarás a ver cómo se juntan tus clases   Realmente son.

  6.   

Entiendo que tu ejemplo está en Ruby, pero esto debería aplicarse en todos los lenguajes OOP.

    
respondido por el Mr. Polywhirl 17.10.2013 - 13:00

Lea otras preguntas en las etiquetas