Lei de Demeter

Olá pessoal, tudo bem?

Hoje vamos falar sobre a Lei de Demeter. É um pattern para orientação objeto que nos ajuda e muito a diminuir o acoplamento, aumentar a manutenibilidade e adaptabilidade de nossos sistemas.

Onde essas palavras difíceis nos ajuda? Ao dar manutenção,sua aplicação não irá sofrer tanto impacto, suas classes irão conhecer apenas a quem deve e com isso as alterações serão mais rápidas e com menos impacto no sistema.

Tudo de bom não é mesmo? Calma aí que depois nós vamos chegar à desvantagem dessa abordagem! (:

Para ver outro post sobre OO basta clicar no link: “Diga, não pergunte!“.

Olhe o código abaixo, em que ele pode ser melhorado?

package com;

public class Main {

    public static void main(String[] args) {
        IAddress address = new Address();
        address.setName("01");
        address.setZipCode("000001");

        IHouse house = new House();
        house.setAddress(address);

        IPerson person = new Person();
        person.setHouse(house);

        // Print the person zip code
        System.out.println(person.getHouse().getAddress().getZipCode());
    }
}

O código acima executa tranquilamente, estamos programando para Interface, nosso código está bem identado e formatado. O que mais poderia melhorar em nosso código?

A Lei de Demeter prega que uma classe não deve conhecer mais do que a quem está sendo invocado. HEIN?! Vamos por partes; repare nossa classe que nossa classe Main quer imprimir o CEP da pessoa, mas para fazer isso a classe Main toma conhecimento de outras duas classes. Caso você não tenha percebido, aí está seu acoplamento!

Repare a classe Main está passando por dentro da classe Person, House e finalmente Address.
Qual a parte ruim dessa história? Imagine que por uma determinação da empresa, a classe Address deixa de existir e a classe House será a responsável por conter o ZipCode.

Em nosso exemplo, será muito fácil fazer essa alteração.Imagine um sistema enorme onde o comando para buscar o ZipCode fosse utilizado quase 100 vezes. Você teria que alterar em 100 lugares diferentes por causa dessa alteração.

A lei de Demeter veio para nos ajudar nesse ponto, com uma pequena alteração em nossa classe Person e House, nós poderemos evitar esse enorme impacto na hora da remoção da classe Address. Veja como nosso código ficará:

package com;

public class Main {

    public static void main(String[] args) {
        IAddress address = new Address();
        address.setName("01");
        address.setZipCode("000001");

        IHouse house = new House();
        house.setAddress(address);

        IPerson person = new Person();
        person.setHouse(house);

        // Print the person zip code
        System.out.println(person.getZipCode());
    }
}
package com;

public interface IPerson {

    void setHouse(IHouse house);

    IHouse getHouse();

    String getZipCode();

}
package com;

public class Person implements IPerson {
    
    private IHouse house;

    @Override
    public void setHouse(IHouse house) {
        this.house = house;
    }

    @Override
    public IHouse getHouse() {
        return house;
    }

    @Override
    public String getZipCode() {
        return house.getZipCode();
    }
}
package com;

public interface IHouse {

    void setAddress(IAddress address);

    IAddress getAddress();

    String getZipCode();

}
package com;

public class House implements IHouse {

    private IAddress address;

    @Override
    public void setAddress(IAddress address) {
        this.address = address;
    }

    @Override
    public IAddress getAddress() {
        return address;
    }

    @Override
    public String getZipCode() {
        return address.getZipCode();
    }
}
package com;

public interface IAddress {

    void setName(String string);

    void setZipCode(String string);

    String getZipCode();

    public abstract String getName();

}
package com;

public class Address implements IAddress {

    private String name;
    private String zipCode;

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void setZipCode(String zipCode) {
        this.zipCode = zipCode;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public String getZipCode() {
        return zipCode;
    }
}

Olhando pelo novo código, imagine qual seria o impacto em nosso código para remover a classe Address. Apenas a classe Home seria alterada, o restante de nosso sistema permaneceria intacto.

Essa é a grande vantagem da Lei de Demeter. Na hora da manutenção seu projeto inteiro terá o menor impacto possível. As adaptações solicitadas pelos responsáveis serão mais simples, menos custosas. No exemplo de hoje, apenas uma classe seria afetada. As demais classes do seu sistema continuariam não sendo afetada se seu sistema continuará com baixo acoplamento.

A desvantagem dessa abordagem é o impacto sobre o desempenho. Você poderia ter uma queda de desempenho caso utilize essa abordagem em um loop como “While, For, …”. Nesse caso, você terá que ver em quais partes do sistema não haveria impacto ao aplicar a lei de Demeter.

Creio que mesma com essa desvantagem sobre o desempenho em alguns lugares localizados, essa abordagem vale a pena ser aplicada em nossos sistemas; a lei de Demeter pode ser utilizada em quase todo o sistema, sendo deixada de lado apenas em locais críticos.

Espero que este post possa te ajudar.

Qualquer dúvida/colocação basta postar.

Até a próxima! o_

Diga, não pergunte!

Às vezes ao programar OO nos deparamos com situações das quais nos sentimos tentados sempre em colocar um valor em um atributo de modo mais rápido. Talvez o prazo para entrega do projeto esteja curto, ou então a alteração em questão é grande e não nos preocupanos com pequenos detalhes… Com isso é mais fácil deixar ser levado pelas circunstâncias do que analisar o que estamos fazendo.

Suponhamos que nós temos uma classe Porta e queremos saber se a ela está aberta ou não, e queremos mais, queremos mudar o estado dela de aberta para fechado e vice-versa. Um modo bem simples seria criar a classe e nela colocarmos um atributo booleano:

 public class Door {
     private boolean doorIsOpen = false;
}

Até aí tudo tranquilo, mas agora começa o perigo. Como iremos fazer com que essa variável tenha seu valor alterado? Devemos optar que essa alteração fosse realizada pela classe Porta? Ou pelo objeto que está chamando o objeto Porta? A solução mais comum que vemos por aí é fazer um get/set muito encontrado em códigos legados:

public void setDoorIsOpen(boolean doorIsOpen) {  
	this.doorIsOpen = doorIsOpen;  
}

public boolean getDoorIsOpen() {  
	return doorIsOpen;  
}  
// To close the Door  
if (door.getDoorIsOpen() == true)  
	door.setDoorIsOpen(false);  

// To open the Door
if (door.getDoorIsOpen() == false)  
	porta.setDoorIsOpen(true);

Pense comigo, e se, essa condição de pergunta (if + get) e o método set estivesse em 30 pontos do sistema e o cliente pedir uma nova regra de negócio. Imaginemos que, para ser fechada, a Porta tem que estar com o alarme ligado:

  public class Door {  
       private boolean doorIsOpen = false;  
       private boolean alarmIsActive = true;  
       public boolean getAlarmIsActive() {  
            return alarmIsActive;  
       }  
  }
  // To close the Door  
  if (door.doorIsOpen() && door.getAlarmIsActive ())  
       door.setDoorIsOpen(false);

Utilizando esse tipo de código, seria necessário ter que alterar todo os método do projeto que utilizassem esse código. Imagine todo o trabalho que teríamos para realizar essa alteração por todo o código…

Para não ter todo esse trabalho, basta dizer ao objeto Porta o que fazer, e não perguntar o estado do seu atributo para depois alterá-lo.

Utilizando esse conceito, alteração do código aconteceria apenas em um ponto e os outros 30 pontos do sistema continuariam intactos. O código inicial, antes da alteração solicitada pelo cliente (alarme ligado), ficaria assim:

  public class Door {  
      private boolean doorIsOpen = false;  
      private boolean alarmIsActive = true;  
      public boolean getAlarmIsActive() {  
           return alarmIsActive;  
      }  
      public boolean isOpen() {  
           return doorIsOpen;  
      }  
       public void closeDoor(){  
          doorIsOpen = false;  
       }  
       public void openDoor(){  
            doorIsOpen = true;  
       }  
  }

E o código para fechar/abrir a Porta seria bem mais simples:

  // Closes the Door
  if (door.isOpen())  
       door.closeDoor();  
  // Opens the Door
  if (!door.isOpen())  
       door.openDoor();

Após essa alteração, para aplicarmos o código novo (verificando o alarme), basta apenas colocar a condição do alarme dentro do método de fechar a Porta, e pronto, alteramos uma linha em uma classe todo o resto do sistema fica intocável:

  public void closeDoor(){  
       if (alarmIsActive)  
            doorIsClose = false;
  }

Agora, toda vez que a regra de negócio mudar (ex.: fechar a Porta), basta apenas alterar um método dentro da classe específica que o restante do projeto continuará intacto.

Utilizando esse princípio, diminuímos o acoplamento entre as classes e seu código está apto a ser utilizado em todas as partes do sistema.

Fica a dica.

Inspirado por: “The pragmatic Bookshelf – Alec Sharp”

Edit: Código final, dica do H.Fernandes. [=