Coesão, Acoplamento e Lei de Demeter

Cesar Augusto Alcancio de Souza
7 min readJul 19, 2020

No ambiente de desenvolvimento Orientado a Objetos, é muito comum a gente escutar desenvolvedores falarem que temos que escrever códigos coeso e com baixo acoplamento (ou acoplamento fraco).

É importante saber que esses são conceitos que surgiram quando começamos a dar mais importância a legibilidade do código e manutenibilidade em detrimento de aspectos da engenharia de software como performance. Alias, esse é um dos motivo do surgimento e popularização de linguagens cada vez mais fáceis de usar (alto nível) referente a linguagens mais performática (baixo nível), por exemplo, Java e Node no lugar da linguagem C (Obs: há controversas e hoje em dia as linguagens de alto nível chegam a ser mais rápidas em determinadas tarefas).

Mas o que Coesão e Acoplamento quer dizer na prática?

Coesão

A palavra coesão no contexto de software, significa algo que é claro e compreensível, isso significa que o código, assim como um texto ou história, deve ser fácil de compreender.

Se fizemos uma analogia com os textos que lemos (historias ou notícias) vemos que eles estão organizados em blocos e cada bloco com objetivo único, diminuindo a complexidade de um assunto e adicionando essa complexidade aos poucos, por exemplo, uma redação possui introdução, desenvolvimento e conclusão. Além disso o desenvolvimento é normalmente dividido em várias etapas para que os temas complexos sejam adicionados sem causar confusão ou ruído.

Caso a redação fosse escrita em outra ordem ou faltando alguma parte seria muito dificil a compreensão. O mesmo acontece com o código, ele precisa ser divididos em partes (classes) que tenham objetivos muito bem definidos. Esse também é um princípio conhecido como Single Responsibility Principle.

Por exemplo, o código abaixo:

É muito comum ver códigos que recebam parâmetros que vão decidir todo o seu comportamento e esse é um exemplo que eu encontrei recentemente em produção.

Não é pouco comum que descobrimos que também temos que registrar essa informação de uma forma diferente caso ela seja de um resultado de erro ou sucesso. Nesse caso o desenvolvedor poderia agregar outro parâmetro para atender essa necessidade, por exemplo.

Com tantos cenários possíveis para esta classe, como fazemos para ter certeza que tudo funciona bem? Qual é a nossa confiança de mudar uma classe com tantas bifurcações? Como são os testes automatizados? Tudo isso é complexidade para a leitura e manutenção do código.

Quanto mais tipos eu precisar persistir (JSON, XML, YAML etc…) mais linhas de códigos eu precisaria agregar a essa classe, fazendo com que a classe tenha que ser constantemente modificada.

Existe outro principio chamado Open Closed Principle que diz que uma classe deve ser aberta para extensão e fechada para modificação, nesse caso, cada vez que eu precise acrescentar uma nova forma de persistir a informação eu precisaria acrescentar mais linhas de códigos a essa classe, o que não está de acordo com o Open Closed Principle. Eu preciso adicionar funcionalidades a essa classe sem precisar alterar ela, como podemos resolver isso?

Existem muitas formas que podemos resolver estes problemas, para este exemplo eu resolveria basicamente com 2 coisas:

  1. Dividindo o método em dois: um para persistir em caso de sucesso e outro para persistir em caso de erro.
  2. Criando uma interface para os diferentes tipos de implementaçōes de persistência: JSON, XML, YAML e etc.

Então veja como ficaria o nosso código. Primeiro a interface:

Depois como ficaria a nossa classe principal:

E por último como ficariam as nossas classes que fazem a implementação de persistir o dado no formato necessário, por exemplo a de XML:

As principais vantagens dessa abordagem é:

  • A classe SingleResponsibilityExample não precisaria ser modificada caso existisse um novo formato necessário a ser persistido.
  • Caso fosse necessário adicionar uma nova funcionalidade (forma de persistir) bastaria criar uma nova classe e implementar a interface PersistableData.
  • Os métodos de sucesso e erro estão separados, deixando claro seu comportamento para quem vai invocar ele.

Como tudo em tecnologia, não existe apenas vantagens, algumas desvantagens podem ser:

  • A princípio (e depois também) pode parecer mais complexo ter várias classes onde antes só existia uma classe, principalmente para quem não está familiarizado com OOP (Objected Oriented Programming) e está acostumado com linguagem procedural.
  • Podem existir códigos repetidos caso a forma de persistir os dados em JSON e XML seja muito parecidas.

O desenvolvedor precisa avaliar bem quando é necessário desmembrar o código em mais classes ou quando isso apenas vai trazer complexidade desnecessária.

Acoplamento

O acoplamento é o quanto uma classe, código, método ou até mesmo aplicação depende de outra.

O acoplamento é sempre mencionado como se fosse o pior dos violões da orientação a objetos, mas é possível fazer código sem se acoplar? Eu desconheço.

O mais importante aqui é tentar acoplar o seu código a outras classes mais sólidas, ou seja que provavelmente não vão mudar, em vez de acoplar a classes menos sólidos que possuem uma maior tendência a mudar e a propagar essa mudança para todos que a estão utilizando.

Por exemplo, a classe Acoplamento tem o metodoA e o metodoB:

O metodoA tem menos chances de mudar. Caso não seja mais necessário implementar a persistência em XML e sim em JSON o método não precisaria fazer nada. Enquanto o metodoB precisaria ser atualizado (quebraria).

É importante notar que sempre estamos acoplados, inclusive ambos os métodos estão acoplados a classe String, mas nesse caso nem notamos pois sabemos que a classe String é sólida e dificilmente mudaria.

O Dependency Inversion Principle é um princípio que trata exatamente este tema, ele propõe que uma classe deve ser acoplada sempre a outras clases mais sólidas que ela, por exemplo, a classe String não pode ser acoplada a classe PersistXML, mas a classe PersistXML pode ser acoplada a classe String e isso serve também para as nossas classes pessoais dos nossos projetos.

É importante ressaltar que essas decisões devem ser tomadas com cuidado, é muito comum projetos onde TODAS classes (sim, todas) possuem uma Interface, por exemplo, IPessoaDao.java para especificar os métodos CRUD da classe PessoaDaoImpl.java, isso é uma aplicação pura do conceito de interface e baixo acoplamento, mas na realidade não traz muitas vantagens já que essa relação de um para um (classe -> interface) NÃO tende a crescer (possuir mais implentações) e quando precisa ser alterada normalmente se altera ambos classe e interface. É preciso avaliar caso a caso e não correr o risco de quebrar o principio You Won’t Need It Principle.

Lei de Demeter

Agora que já sabemos o que é acoplamento e como gerenciá-lo a nosso favor, podemos conhecer outro princípio que também tem o objetivo de diminuir o acoplamento entre as classes e evitar a propagação de erros em caso de mudanças.

Apesar do nome a Lei de Demeter é muito simples e diz o seguinte:

Deve haver conhecimento limitados entre as classe e elas devem apenas conversar com suas dependências diretas.

É muito comum a gente ver o seguinte código:

Não parece existir um problema muito grande com este código, porém a classe que procesa o Cliente está com acceso diretamente a lista de itens, ou seja, pode até modificá-la (caso não seja usado nenhuma função que impeça isso, por exemplo, Collections.unmodifiablelist).

A Lei de Demeter diz que a classe que processa o cliente não poderia ter acesso a Compra e Itens, apenas poderia acessar a classe Cliente. Assim caso ocorra alguma mudança na classe Compra ou Item a classe que processa o Cliente não seria afetada.

E como isso poderia ser resolvido?

Classe Principal

Classe Cliente

Classe Compra

Agora em vez da classe Compra expor os itens diretamente, ela apenas expõe o tamanho da lista. A classe Cliente por sua vez repassa esse método para a classe Principal.

Agora a classe Principal não tem conhecimento da Compra e do Item, caso aconteça alguma mudança nessa classe não afetaria a classe principal.

Além da Coesão e Acoplamento foi apresentado alguns dos conceitos SOLID (Single Responsibility, Open Closed, Liskov Substitution, Interface Segregation e Dependency Inversion), espero que esse artigo possa ter ajudado a entender alguns desses princípios de desenvolvimento orienado a objetos. Sempre reforçando que a simplicidade está diretamente ligado a capacidade de legibilidade de um código e esses (e qualquer) princípio deve ser utilizado com bom senso.

--

--

Cesar Augusto Alcancio de Souza

Sofware Engineer Lead, focused on development and maintenance of products