O objetivo deste tópico é abordar um princípio de OOD (Object-Oriented Design - Projeto Orientado a Objetos) chamado Lei de Demeter e junto aproveitar para falar do anti-padrão Train Wreck e sua possível cura, o princípio "Tell,Don't Ask" (Diga, não Peça).
Lei de Demeter
A Lei de Demeter é fácil de implementar e de entender, talvez o difícil de compreender seja o porquê, em outras palavras, compreender a motivação. A Lei de Demeter também é conhecida como Princípio do Menor Conhecimento.
De forma resumida, a Lei diz o seguinte: "Uma classe ou método deve ter um conhecimento limitado de outras classes e métodos, devendo comunicar-se apenas com as classes imediatamente mais próximas".
Formalmente diz que um método M de uma classe C só deve chamar métodos de:
C
Um objeto criado por M
Um objeto passado como parâmetro para M
Um objeto dentro de uma instância de C
Recentemente, em um projeto de exemplo usado em aula, dá para ilustrar bem a aplicação e a violação da Lei de Demeter. Por exemplo, temos uma classe GestorJanela, que tem internamente uma instância de JDesktopPane.
class GestorJanela { // ... public JDesktopPane getAreaTrabalho() { return areaTrabalho; } }
A partir de uma ação ou método dentro de uma Janela eu posso trazer uma Janela para frente usando o seguinte código:
GestorJanela.getInstance().getAreaTrabalho().getDesktopManager().activateFrame(janela);
Este código viola a Lei de Demeter porque além de conhecer o GestorJanela, ele também conhece a implementação de JDesktopPane ao usar getAreaTrabalho, DesktopManager e do método activateFrame. Se qualquer parte deste código mudar todos os lugares onde é chamado também deverão mudar. Por exemplo, se o GestorJanela passar a usar outro Painel ao invés do JDesktopPane para embutir as janelas, todos os chamadores quebrarão.
Train Wreck
Este mesmo código também é um exemplo do anti-padrão Train Wreck. Ele é encontrado quando existem métodos getIsso().getAquilo().getAquiloOutro().facaAlgo()
O que fazer então? Delegar! Quem deve subir a Janela é o GestorJanela (o nome não é por acaso). A implementação ficaria assim:
class GestorJanela { // ... public void trazerJanelaParaFrente(JInternalFrame janela) { areaTrabalho.getDesktopManager().activateFrame(janela); // ainda viola o princípio aqui, mas não temos como // alterar o comportamento de JDesktopPane // para não expor o DesktopManager, fazendo parte do Swing // (em outras palavras, a culpa não é nossa) } }
E os chamadores fariam assim:
GestorJanela.getInstance().trazerJanelaParaFrente(janela);
Dá para melhorar este código removendo o Train Wreck:
GestorJanela gestor = GestorJanela.getInstance(); gestor.trazerJanelaParaFrente(janela);
Um colega perguntou: "Vale a pena adicionar mais código executando a mesma funcionalidade?". A resposta é: "Vale". Para manutenções e revisões de código, a implementação deve tão simples quanto possível, permitindo entendê-la com uma passada de olhos. Se são necessárias duas ou mais passadas, então talvez esteja na hora de refatorar e dividir as funcionalidades/responsabilidades.
Tell, Don't Ask
O código de exemplo também é um bom exemplo do princípio Tell, don't Ask. Não seria Tell, don't Ask se fosse implementado assim:
GestorJanela.getInstance().setJanelaTopo(janela);
Métodos get e set são úteis para expor propriedades, não para definir comportamento. Logo, em geral, o hipotético código:
loginController.getUsuarioLogado().getPermissao().setHabilitado(false);
é melhor escrito assim:
loginController.desabilitaPermissao();
Diga, não peça.
Bibliografia:
Martin, Robert C. Código Limpo.
Wikipédia. Law of Demeter.