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_

6 thoughts on “Lei de Demeter

  1. Olá, este é um conceito que nunca me senti confortável em usar, há uma grande quantidade de escrita de métodos só para passar a mensagem e acaba que o Pessoa fica interfaceando todos os possíveis métodos que serão chamados e a mensagem passando por dentre todos os objetos.

    Porque Pessoa deve fornecer diretamente o endereço e não a Casa ?

    Pra mim código bom é código enxuto e legível, se precisar escrever trocentas coisas pra fazer algo simples que talvez num futuro muito distante possa ser radicalmente alterada e com uma baixa probabilidade é perda de tempo.

    • Olá Maiko, boa tarde.

      Concorde que você tem uma sobrecarga de método sobre as classes deixado as vezes até um código “repetido” como no exemplo do post.

      Mas repare que você tem um ganho muito bom de tempo e baixo impacto ao se fazer refatoração do sistema.

      Caso seu código seja muito refatorado, em constante implementação (adicionando novas funcionalidades ou manutenção mesmo \ refactoring) valeria a pena utilizar essa técnica pois haveria um ganho de tempo e baixo impacto. E baixo impacto te proporciona menor risco de bugs ao subir uma nova funcionalidade/correção.

      Caso seja uma classe que realmente vai ser escrita apenas uma vez e vai ficar quieta durante muito tempo (como por exemplo um DAO genérico, ou uma classe User) não valeria muito utilizar esse patern.

      Creio que é uma boa abordagem caso aplicado no caso correto, e como você disse, em certos casos seria apenas um overhead.

      Obrigado pela opinião, volte sempre. [=

  2. Poderia exemplificar o caso de Endereço não exisitr mais?
    Pois eu penso que de qualquer forma no seu código vai ter uma chamada a setAddress(IAddress address) , pois você vai ter que setar quem implementa a interface em House, ou seja quando deixar de existir você não vai ter que alterar no seu main o set? será que você poderia mostrar abaixo como fica sua primeira classe main, com as alterações, para podermos enchergar tal benefíco, obrigado.

    • Olá Claudemir, bom dia.

      Apesar de em algum momento a chamada para se passar o endereço ter que existir, não serão os usuários da classe Person mais que deverão sofrer alterações quando o endereço mudar.

      Utilizando o exemplo acima, imagine que a classe Address deixe de existir, apenas a classe House seria alterada para adicionar os campos de Address.

      Nossa classe Person continuaria a mesma para as classes que acessarem os seus métodos (em nosso caso, a classe Main).
      Note que no código abaixo não temos mais a classe Address, mas a Person continua com a mesma chamada à person.getZipCode() e acrescentei o person.getAddressName(). A vantagem é que, quem estiver utilizando person.getZipCode() nunca iria saber que o a classe Address deixou de existir.

      IHouse house = new House();
      house.setAddress(address);
      house.setName("01");
      house.setZipCode("000001");
      IPerson person = new Person();
      person.setHouse(house);
      person.getZipCode();
      person.getAddressName();

      Espero ter te ajudado, até a próxima.

  3. Boa noite Hebert.

    Suponha a seguinte situação:
    Eu tenho uma classe Categoria outra classe Subcategoria e outra Item.
    Onde uma Categoria tem várias(vinculo no banco) Subcategorias e uma Subcategoria tem vários Itens (Observe que Categoria e Item não tem vinculo diretamente).

    E para acessar o id de uma Categoria através de um Item eu devo passar pelo nível de Subcategoria, por exemplo: item.getSubcategoria,getCategoria.getId()

    Caso o nível Subcategoria for excluído, eu vou ter que fazer várias modificações no meu código, como você citou no post.

    A solução seria criar um relacionamento entre Categoria e Item, prevendo que algum dia Subcategoria poderá deixar de existir ?
    Ou eu devo criar um método get para cada atributo da classe Categoria em cada classe de nível abaixo de Categoria ? Mesmo que Categoria tenha vários atributos?

    Não sei se minha explicação ficou clara, mais eu já passei por essa situação.
    Eu não vinculei a classe de nível mais superior(nesse caso categoria) com o nível mais inferior(nesse caso Item), pois achei que seria um relacionamento desnecessário porque eu conseguiria acessar meu objeto indiretamente da mesma forma.O problema é que se fosse necessário excluir um nível do meio, daria muito retrabalho.

    Desculpa pelo texto grande.

    Continue com seus posts, são muito bons.

    Obrigado.

    • Júnior, boa tarde.

      Se você está indo criar uma classe já pensando em destruí-la, tem algo aí cheirando mau. ^^

      Eu já fiz algo parecido utilizando listas. Por exemplo, a classe Carro teria teria uma lista de Pessoas dentro de um Set.

      Eu não queria expor o Set para evitar diversos tipos de problemas caso fosse necessário trocar para list. Ou até mesmo manobras para evitar NullPointer e erros de iteração.

      Nesse caso, a classe carro era quem expunha os métodos add, remove e outros. Ficou algo do tipo carro.addPessoa(pessoa) e esse método era responsável por chamar a lista.

      Só tome cuidado para a classe não ficar com muitos métodos e ficar muito grande. Uma classe grande facilita a introdução de bugs na aplicação, sem falar que é má prática.

      Espero ter ajudado,

      Agradeço muito a visita.

Leave a Comment