Estou lendo um livro chamado Rails AntiPatterns e eles falam sobre o uso de delegação para evitar violar a Lei de Demeter. Aqui está o seu exemplo principal:
Eles acreditam que chamar algo assim no controlador é ruim (e eu concordo)
@street = @invoice.customer.address.street
A solução proposta é fazer o seguinte:
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
Eles estão afirmando que, como você usa apenas um ponto, não está violando a Lei de Deméter aqui. Acho que isso está incorreto, porque você ainda está passando pelo cliente para passar pelo endereço e obter a rua da fatura. Eu obtive essa ideia principalmente de uma postagem de blog que li:
http://www.dan-manges.com/blog/37
Na postagem do blog, o principal exemplo é
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
Os estados postagem no blog que apenas um, embora haja pontilham customer.cash
em vez de customer.wallet.cash
, este código ainda viola a Lei de Demeter.
Agora, no método Paperboy collect_money, não temos dois pontos, apenas um em "customer.cash". Esta delegação resolveu o nosso problema? De modo nenhum. Se observarmos o comportamento, um entregador de jornal ainda está chegando diretamente na carteira de um cliente para sacar dinheiro.
EDITAR
Entendo e concordo completamente que isso ainda é uma violação e preciso criar um método Wallet
chamado de retirada que lide com o pagamento e que eu deva chamar esse método dentro da Customer
classe. O que não entendo é que, de acordo com esse processo, meu primeiro exemplo ainda viola a Lei de Deméter, porque Invoice
ainda está alcançando diretamente Customer
a rua.
Alguém pode me ajudar a esclarecer a confusão. Eu tenho procurado nos últimos 2 dias tentando deixar este tópico afundar, mas ainda é confuso.
fonte
Respostas:
Seu primeiro exemplo não viola a lei de Demeter. Sim, com o código como está, dizer
@invoice.customer_street
que obtém o mesmo valor que um hipotético@invoice.customer.address.street
teria, mas a cada passo da travessia, o valor retornado é decidido pelo objeto que está sendo solicitado - não é que "o entregador chegue ao carteira do cliente ", é que" o entregador pede dinheiro ao cliente e o cliente recebe o dinheiro da carteira ".Quando você diz
@invoice.customer.address.street
, está assumindo o conhecimento dos clientes e endereços internos - isso é ruim. Quando você diz@invoice.customer_street
, está perguntandoinvoice
: "Ei, eu gostaria da rua do cliente, você decide como obtê-la ". O cliente então diz para o seu endereço: "ei, eu gostaria da sua rua, você decide como obtê-la ".O impulso de Deméter não é 'você nunca pode conhecer valores de objetos distantes no gráfico' "; é sim 'você mesmo não deve percorrer muito longe o gráfico de objetos para obter valores'.
Concordo que isso possa parecer uma distinção sutil, mas considere o seguinte: no código compatível com Demeter, quanto código precisa ser alterado quando a representação interna de uma
address
alteração? E o código não compatível com Demeter?fonte
O primeiro exemplo e o segundo não são realmente muito iguais. Enquanto o primeiro fala sobre regras gerais de "um ponto", o segundo fala mais sobre outras coisas no design de OO, especialmente " Diga, Não pergunte "
Novamente, " apenas por comportamento, não por atributos "
Se você pedir atributos, você deve perguntar . "Ei, cara, quanto dinheiro você tem no bolso? Mostre-me, vou avaliar se você pode pagar isso." Isso é errado, nenhum balconista de compras se comportará assim. Em vez disso, eles dirão "Por favor, pague"
Será dever do cliente avaliar se ele deve pagar e se ele pode pagar. E a tarefa do funcionário é concluída depois de pedir ao cliente para pagar.
Então, o segundo exemplo prova que o primeiro está errado?
Na minha opinião. Não , desde que:
1. Você faz isso com autocontrole.
Embora você possa acessar todos os atributos do cliente
@invoice
por delegação, raramente é necessário em casos normais.Pense em uma página mostrando uma fatura em um aplicativo Rails. Haverá uma seção no topo para mostrar os detalhes do cliente. Então, no modelo de fatura, você codificará assim?
Isso é errado e ineficiente. Uma abordagem melhor é
Em seguida, deixe o cliente parcial para processar todos os atributos pertencentes ao cliente.
Então geralmente você não precisa disso. Mas você pode ter uma página de lista mostrando todas as faturas recentes, há um campo de informações em cada um
li
exibindo o nome do cliente. Nesse caso, você precisa mostrar o atributo do cliente e é totalmente legítimo codificar o modelo como2. Não há nenhuma ação adicional, dependendo desta chamada de método.
No caso acima da página da lista, a fatura perguntou o atributo de nome do cliente, mas seu objetivo real é " mostre seu nome "; portanto, ainda é basicamente um comportamento, mas não um atributo . Não há mais avaliação e ação com base nesse atributo, se o seu nome for "Mike", eu gostarei de você e lhe darei 30 dias a mais de crédito. Não, a fatura apenas diz "mostre seu nome", não mais. Portanto, isso é totalmente aceitável, de acordo com a regra "Tell Don't Ask" no exemplo 2.
fonte
Leia mais no segundo artigo e acho que a idéia ficará mais clara. A idéia é apenas oferecer ao cliente a capacidade de pagar e ocultar completamente onde o caso é mantido. É um campo, um membro de uma carteira ou outra coisa? O chamador não sabe, não precisa saber e não muda se esse detalhe da implementação mudar.
Acho que sua segunda referência está dando uma recomendação mais útil.
A única idéia de "um ponto" é um sucesso parcial, pois esconde alguns detalhes profundos, mas ainda aumenta o acoplamento entre componentes separados.
fonte
Parece que Dan derrotou seu exemplo neste artigo: The Paperboy, The Wallet e The Law Of Demeter
Entendo que seu exemplo está em Ruby, mas isso deve se aplicar a todos os idiomas OOP.
fonte