.NET - A lei de Demeter (LoD)


O acoplamento em um software orientado a objetos indica o nível no qual as classes são dependentes umas das outras. Um sistema fortemente acoplado é geralmente mais difícil de manter e modificar do que um sistema fracamente acoplado ou flexível.

A Lei de Demeter aborda um problema específico do acoplamento e apresenta princípios para organizar e reduzir as dependências entre classes.

Obs: Dizemos que existe uma dependência entre duas classes quando uma delas faz referência à outra através da execução de um método.

Acoplamento

- Acoplamento é o nível de dependência/conhecimento que pode existir entre as classes;
- Uma classe com acoplamento fraco não é dependente de muitas classes para fazer o que ele tem que fazer;
- Uma classe com acoplamento forte depende de muitas outras classes para fazer o seu serviço;
- Uma classe com 
acoplamento forte é mais difícil de manter, de entender e de ser reusada;

Coesão

- Coesão é o nível de integralidade interna de uma classe; (Veja o principio da responsabilidade única - SRP)
- A coesão Mede o grau que um classe ou seus métodos fazem sentido, ou seja, quão claro é o entendimento do que a classe ou método possui
- Uma classe com alta coesão possui responsabilidades bem definidas e são difíceis de serem desmembradas em outras classes;
- Uma classe com baixa coesão possui muitas responsabilidades, geralmente que pertencem a outras classes, e podem ser facilmente desmembradas em outras classes;
- Uma classe com 
baixa coesão é difícil de entender, manter e reusar;

Portanto quanto mais forte o acoplamento e mais baixa a coesão de uma classe mais difícil ela será de entender, manter e ser reusada.

O que é afinal a lei de Demeter ?

A Lei de Demeter foi descrita pela primeira vez na Universidade Northeastern, em Boston, Massachusetts em 1987. Na verdade essa "Lei" fornece um conjunto de orientações úteis para o projeto de software. Quando estas regras são seguidas reduzimos o acoplamento, descrevendo como os métodos e propriedades das classes devem se comunicar entre si. A lei é por vezes conhecida como o Princípio do Mínimo Conhecimento. (Ou ainda: fale somente com seus amigos mais próximos.)

A lei especifica que os métodos e propriedades de uma classe só devem invocar os membros dos seguintes objetos:

- O objeto no qual o método ou propriedade é declarada;
- Os objetos que são passados para o membro utilizando os seus parâmetros;
- Os objetos que são declarados dentro da mesma classe como o método de chamada ou propriedade. Estes são os objetos componentes diretos da classe que contém o membro;
- Os objetos globais, embora estes geralmente devam ser minimizados;

Dessa forma um método A de um objeto O somente poderia acessar métodos de outros objetos segundo as seguintes regras:

  1. Seja um parâmetro de A;
  2. seja um objeto que A criou;
  3. Seja um método do próprio objeto O;
  4. Seja um objeto diretamente relacionado com o objeto O;
  5. Seja uma variável global acessível ao objeto O;

A seguir temos um exemplo que nos ajudará a entender melhor a lei de Demeter.

Vamos considerar um pequeno conjunto de classes que simula um controle remoto genérico que rotaciona um objeto mecânico.

Por questão de simplicidade teremos uma classe chamada ControleRemoto que possui dois métodos que rotaciona o objeto mecânico para no sentido horário e no sentido anti-horário.

A classe que representa o objeto mecânico possui um campo público que representa um motor que controla a rotação do dispositivo.

A classe Motor controla a rotação do objeto e pode movimentá-lo no sentido horário e no sentido anti-horário em uma quantidade definida de movimentos definido na classe ControleRemoto.

Abaixo temos o código das classes descritas acima em uma aplicação Console feita no Visual C# 2010 Express Edition com o nome ControleRemoto:

- Classe ControleRemoto

 public class ControleRemoto
    {
        public ObjetoMecanico _objMec = new ObjetoMecanico();

        public void RotacaoSentidoHorario()
        {
            _objMec.RotacaoMotor.SentidoHorario(10);
        }

        public void RotacaoSentidoAntiHorario()
        {
            _objMec.RotacaoMotor.SentidoAntiHorario(10);
        }
    }

- Classe ObjetoMecanico

public class ObjetoMecanico
{
   public Motor RotacaoMotor = new Motor("Rotacionar");
}

- Classe Motor

   public class Motor
    {
        string _nomeMotor;

        public Motor(string nomeDoMotor)
        {
            _nomeMotor =  " Motor" + nomeDoMotor ;
        }

        public void SentidoHorario(int movimentos)
        {
            Console.WriteLine("{0} rotacionou no sentido horário {1} movimentos", _nomeMotor, movimentos);
        }

        public void SentidoAntiHorario(int movimentos)
        {
            Console.WriteLine("{0} rotacionou no sentido anti-horário {1} movimentos", _nomeMotor, movimentos);
        }
    }

Um exemplo de utilização dessas classes , de forma bem simplificada pode ser vista a seguir :

    class Program
    {
        static void Main(string[] args)
        {
            ControleRemoto controle = new ControleRemoto();
            controle.BotaoParaFrente();
            controle.BotaoParaTras();
            Console.ReadKey();
        }
    }

O código acima e o resultado do exemplo mostra que as classes funcionam corretamente e podemos movimentar o objeto usando os seus métodos sem problemas nem erros.

Mas temos um problema com a classe ControleRemoto que viola a lei de Demeter. Percebeu ????

Não percebeu !!!

Então vamos entender ...

Na classe ControleRemoto quando qualquer botão for pressionado, estamos usando os métodos RotacaoSentidoHorario e RotacaoSentidoAntiHorario da classe Motor a partir da classe ObjetoMecanico.

O que estamos fazendo aqui é chamar um método de um objeto que estamos obtendo a partir de outro objeto:

_objMec.RotacaoMotor.SentidoHorario(10);

- Método SentidoHoriario obtido do objeto MovimentoMotor que é obtido do objeto _objMec

- A chamada não é feita para um membro da classe, um parâmetro, para um objeto que o método instanciou, um objeto global ou para um membro do componente da classe;

A chamada de métodos da classe Motor pela classe ControleRemoto criou um forte acoplamento entre as classes. A classe ControleRemoto sabe muito sobre os detalhes de movimentação do objeto mecânico e uma classe não deve conhecer mais do que a quem está sendo invocado.

Qualquer alteração na classe Motor irá impactar a classe ControleRemoto e para resolver este problema vamos aplicar as diretrizes da lei de Demeter para minimizar este acoplamento.

Aplicando a lei de Demeter

A primeira coisa a fazer é remover o acoplamento existente na classe ControleRemoto. Vamos alterar o código desta classe conforme mostrado a seguir:

public class ControleRemoto
 {
        public ObjetoMecanico _objMec = new ObjetoMecanico();

        public void RotacaoSentidoHorario()
        {
            _objMec.RotacionarObjeto(10);
        }

        public void RotacaoSentidoAntiHorario()
        {
            _objMec.RotacionarObjeto(-10);
        }
 }

Note que agora criamos dois métodos, um para rotacionar no sentido horário e outro para o sentido anti-horário e removemos a chamada aos métodos da classe Motor.

Teremos que ajustar agora a classe ObjetoMecanico criando o método RotacionarObjeto que estamos chamando na classe ControleRemoto conforme o código abaixo:

  public class ObjetoMecanico
    {
        public Motor rotacaoMotor = new Motor("Rotacionar");

        public void RotacionarObjeto(int graus)
        {
            if (graus > 0)
                rotacaoMotor.SentidoHorario(graus * 10);
            else
                rotacaoMotor.SentidoAntiHorario(graus * -10);
        }
    }

No método RotacionarObjeto que criamos estamos usando os métodos da classe Motor de forma que a classe ControleRemoto não chama mais diretamente os métodos da classe Motor. A classe Motor não sofrerá nenhuma alteração.

Dessa forma reduzimos o acoplamento entre a classe ControleRemoto e a classe Motor e satisfazemos a lei de Demeter pois agora a classe ControleRemoto utilizar o método RotacionarObjeto da classe ObjetoMecanico para rotacionar o objeto.

Agora se houver qualquer alteração na classe Motor teremos que ajustar somente a classe ObjetoMecanico.

È uma boa prática seguir as regras da Lei de Demeter ao desenvolver suas classes. No entanto, há algumas situações no qual o benefício obtido não é tão sensível pois existem ocasiões nos quais o acoplamento é justamente o que desejamos obter.

Pode parecer uma contradição mas quando estamos realizando a vinculação de dados com objetos vamos precisar que os objetos estejam acoplados para realizar a vinculação.

Então se você deparar com código realizando chamadas de métodos encadeadas como: a.getB().getC().Metodo()

Fique atento e verifique se não seria o caso de aplicar a lei de Demeter para reduzir o acoplamento.

Pegue a solução completa aqui: ControleRemoto.zip

Gál 1:10 Pois busco eu agora o favor dos homens, ou o favor de Deus? ou procuro agradar aos homens? se estivesse ainda agradando aos homens, não seria servo de Cristo.

Gál 1:11 Mas faço-vos saber, irmãos, que o evangelho que por mim foi anunciado não é segundo os homens;

Gál 1:12 porque não o recebi de homem algum, nem me foi ensinado; mas o recebi por revelação de Jesus Cristo.

Gál 1:13 Pois já ouvistes qual foi outrora o meu procedimento no judaísmo, como sobremaneira perseguia a igreja de Deus e a assolava,

Gál 1:14 e na minha nação excedia em judaísmo a muitos da minha idade, sendo extremamente zeloso das tradições de meus pais.

Referências:


José Carlos Macoratti