19 de abr. de 2011

Lei de Demeter, Train Wrecks e Tell, Don't Ask

Salve,

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.