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:
Creen que llamar a 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
Afirman que dado que solo usa un punto, no está violando la Ley de Deméter 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í:
http://www.dan-manges.com/blog/37
En la publicación del blog, el mejor ejemplo 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 en customer.cash
lugar de customer.wallet.cash
este, este código aún viola la Ley de Demeter.
Ahora en el método Paperboy collect_money, no tenemos dos puntos, solo tenemos uno en "customer.cash". ¿Esta delegación ha resuelto nuestro problema? De ningún modo. Si observamos el comportamiento, un repartidor de periódicos todavía está buscando directamente en la billetera de un cliente para sacar dinero.
EDITAR
Entiendo completamente y acepto que esto sigue siendo una violación y que necesito crear un método Wallet
llamado retiro que maneja el pago por mí y que debería llamar a ese método dentro de la Customer
clase. Lo que no entiendo es que, de acuerdo con este proceso, mi primer ejemplo todavía viola la Ley de Deméter porque Invoice
todavía está llegando directamente Customer
a la calle.
¿Alguien puede ayudarme a aclarar la confusión? He estado buscando durante los últimos 2 días tratando de dejar que este tema se hunda, pero aún es confuso.
fuente
Respuestas:
Su primer ejemplo no viola la ley de Demeter. Sí, con el código tal como está, decir
@invoice.customer_street
que sucede que tiene el mismo valor que un hipotético@invoice.customer.address.street
, pero en cada paso de la travesía, el valor devuelto es decidido por el objeto al que se le pregunta : no es que "el repartidor llegue al billetera del cliente ", es que" el repartidor de periódicos le pide efectivo al cliente, y el cliente obtiene el efectivo de su billetera ".Cuando dices
@invoice.customer.address.street
, estás asumiendo el conocimiento de clientes y direcciones internas: esto es lo malo. Cuando dices@invoice.customer_street
, estás preguntandoinvoice
, "oye, me gustaría la calle del cliente, tú decides cómo la obtienes ". El cliente luego dice a su dirección, "oye, me gustaría tu calle, tú decides cómo la obtienes ".El objetivo de Demeter no es "nunca se pueden conocer los valores de los objetos lejanos en el gráfico", sino que "usted mismo no debe atravesar el gráfico del objeto 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 código debe cambiar cuando cambia la representación interna de un
address
? ¿Qué pasa con el código no compatible con Demeter?fuente
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 " Dile, no preguntes "
Nuevamente, " solo por comportamiento, no por atributos "
Si pide atributos, se supone que debe preguntar . "Oye, ¿cuánto dinero tienes en el bolsillo? Muéstrame, evaluaré si puedes pagar esto". Eso está mal, ningún empleado de compras se comportará así. En cambio, dirán: "Por favor pague"
Será deber del cliente evaluar si debe pagar y si puede pagar. Y la tarea del secretario termina después de decirle al cliente que pague.
Entonces, ¿el segundo ejemplo prueba que el primero está mal?
En mi opinión. No , siempre que:
1. Lo haces con autolimitación.
Si bien puede acceder a todos los atributos del cliente
@invoice
por delegación, rara vez lo necesita en casos normales.Piense en una página que muestra 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 la factura, ¿codificará así?
Eso está mal e ineficiente. Un mejor enfoque es
Luego, deje que el cliente procese todos los atributos que pertenecen al cliente.
Así que generalmente 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 una que
li
muestra el nombre del cliente. En este caso, necesita que se muestre el atributo del cliente, y es totalmente legítimo codificar la plantilla como2. No hay más acciones dependiendo de esta llamada al método.
En el caso anterior de la página de lista, la factura preguntaba el atributo del nombre del cliente, pero su propósito real es " muéstrame tu nombre ", por lo que básicamente sigue siendo un comportamiento pero no un atributo . No hay más evaluaciones y acciones basadas en este atributo, por ejemplo, si tu nombre es "Mike", me agradarás y te daré 30 días más de crédito. No, la factura solo dice "muéstrame tu nombre", no más. Entonces, eso es totalmente aceptable de acuerdo con la regla "Dile, no preguntes" en el ejemplo 2.
fuente
Lea más en el segundo artículo y creo que la idea será más clara. La idea es simplemente que el cliente ofrezca la capacidad de pagar y esconderse completamente donde se guarda el caso. ¿Es un campo, un miembro de una billetera o algo más? La persona que llama no lo sabe, no necesita saberlo y no cambia si ese detalle de implementación cambia.
Así que creo que su segunda referencia está dando una recomendación más útil.
La idea única de "un punto" es un éxito parcial, ya que oculta algunos detalles profundos, pero aún así aumenta el acoplamiento entre componentes separados.
fuente
Suena como que Dan obtuvo su ejemplo de este artículo: The Paperboy, The Wallet y The Law Of Demeter
Entiendo que su ejemplo está en Ruby, pero esto debería aplicarse en todos los idiomas de OOP.
fuente