SOLID – Dependency Inversion Principle (DIP)

Boa noite, leitores! Tudo bem?
A série de artigos sobre os princípios SOLID termina hoje! Para fechar com chave de ouro, conheceremos o Dependency Inversion Principle, bastante discutido na comunidade de programadores e considerado como o mais importante entre os cinco princípios. Mas há todo um motivo para isso. Confira!

Introdução

Muito se fala sobre alto acoplamento em programação. Este termo se refere às fortes dependências entre classes, resultando em uma situação muito comum: uma simples alteração em uma classe impacta em várias classes adjacentes.

O Dependency Inversion Principle – ou DIP – orienta uma forma de inverter as dependências (como o próprio nome diz), elevando o nível de abstração em uma arquitetura. O objetivo é modificá-la para que que as dependências sejam representadas por abstrações, reduzindo o acoplamento.

Antes de continuar, vale uma observação importante: Inversão de Dependência e Injeção de Dependência são conceitos diferentes, ok? Este último ainda será abordado aqui no blog.

Pois bem, como uma demonstração dessas fortes dependências, considere o construtor da classe abaixo:

Observe que a classe recebe um objeto de um tipo concreto como parâmetro. Isso nos “força” a instanciar um objeto exclusivo desse tipo (TArquivoZip) ao utilizar o leitor. A princípio não há problemas, porém, caso surja a necessidade de interpretar um formato diferente de arquivo compactado, tanto a classe do leitor quanto a classe do arquivo receberão alterações que poderão, talvez, impactar em funcionalidades já existentes.

Mas há uma alternativa. Ao invés de alterá-las, podemos criar novas classes. Imagine, por exemplo, um novo leitor de arquivos com extensão RAR. Criaríamos então as classes TLeitorArquivoRar e TArquivoRar, certo?

Errado! Se fizermos isso, quebraríamos o conceito de abstração, pois certamente haveriam vários comportamentos em comum entre essas classes, e código duplicado não é uma boa ideia, concorda? 🙂

Para iniciar, julgo importante reproduzir as duas definições do DIP:

  • Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações;
  • Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.

Quanto à primeira definição, poderíamos citar, por exemplo, uma classe básica na arquitetura que depende de uma classe muito específica. Quando isso ocorre, há justamente uma dependência que deveria ser invertida.

A segunda definição simplesmente condiz com o fato de que uma Interface não deve receber tipos concretos nos parâmetros de suas assinaturas. Se analisarmos bem, as duas definições estão relacionadas, já que mencionam o conceito de abstração.

Cenário de exemplo

Vamos entender melhor isso tudo?

A minha esposa, mais uma vez, elaborou um ótimo exemplo para a parte prática deste artigo. Codificaremos um sistema de login que recebe as credenciais do usuário para conceder o acesso. Devo enfatizar, claro, que não haverá a codificação da validação do login, já que foge um pouco do foco do artigo, ok?

Considere a seguinte classe abaixo:

O método SetCredenciais é um mero setter para atribuir o valor à variável de classe FCredenciais. Não há nada de errado com essa classe. Pelo menos por enquanto.

Após algum tempo, o cliente solicita o desenvolvimento de um módulo mobile do sistema. A autenticação do aplicativo deve se comportar da mesma forma, solicitando um login para acesso. Porém, não podemos mais usar a classe existente (TLoginSistema), já que ela é exclusiva para o módulo desktop. Qual seria a solução?

Simples! Criar a classe “TLoginAplicativo”!
É uma opção realmente simples, mas não corresponde com as diretrizes da orientação a objetos. Devemos tirar proveito do conceito de abstração, lembram-se?

Codificaremos, então, uma Interface que declara os dois métodos principais para a autenticação:

Boa! Basta criar duas classes que implementam essa Interface!

Opa… mas espere aí. Sinto algo de estranho. Vamos recapitular uma das definições do DIP:

“Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.”

No código acima, a Interface ILogin, que é uma abstração, depende de TCredenciaisSistema, que é um tipo concreto (ou detalhe), portanto, o Dependency Inversion Principle foi violado. Além disso, ILogin é uma estrutura de alto nível, que servirá de base para qualquer tipo de login (desktop, web, mobile…), enquanto TCredenciaisSistema é uma estrutura de baixo nível, já que condiz apenas com o módulo desktop.

Essa violação pode se acentuar ainda mais caso surja um novo tipo de credenciais, como, por exemplo, usar os dados do Facebook para acessar o aplicativo móvel, visto que já é uma funcionalidade bem comum.

Aplicando o DIP

Para eliminar essa violação, devemos inserir mais abstrações no código. Codificaremos mais uma Interface, dessa vez, referente às credenciais:

O próximo passo é alterar a Interface de login para receber uma abstração como parâmetro:

Por meio dessa simples alteração, invertemos a dependência: agora, os detalhes dependem de abstrações. Quer ver? Observe a diferença na classe que criamos no início do artigo:

Notou que não há mais referência à classe concreta TCredenciaisSistema? 🙂

A classe de login do aplicativo móvel também seria semelhante, com exceção do algoritmo de boas vindas após validar o acesso. As credenciais, por sua vez, também implementariam abstrações:

Já conseguimos identificar grandes vantagens, mas não é só isso!

A utilização dessas classes automaticamente se torna abstrata também. Veja o exemplo a seguir:

Por este código, você consegue identificar se o login está sendo feito no sistema desktop ou no aplicativo móvel?

Não? Esse é o objetivo!

Ao utilizar os tipos ILogin e ICredenciais, não importa, de antemão, qual é o sistema e o tipo de credenciais que serão utilizados. Essas variáveis podem receber qualquer tipo concreto, desde que eles implementem as Interfaces mencionadas. Para melhorar, há um Factory Method que encapsula o tipo de classe a ser retornado. Muito bom, hein? 😀

Antes de encerrar essa série de artigos, vale observar que a proposta do DIP envolve implicitamente os quatro primeiros princípios:

  • Ao declarar abstrações e criar implementações específicas a partir delas, satisfazemos o SRP;
  • Trabalhar com variáveis do tipo de Interfaces e instanciar tipos concretos em tempo de execução nos remete ao propósito do OCP;
  • Abstrações favorecem a modelagem correta de classes e subclasses, cumprindo o LSP;
  • A inversão de dependência promove a segregação de Interfaces, representada pelo ISP, já que novas estruturas de alto nível são elaboradas.

São estes motivos que tornam o Dependency Inversion Principle tão importante! 😉

Obrigado por acompanhar mais essa série de artigos, pessoal!
Volto logo com algumas dicas em Delphi. Abraço!


 

André Celestino