O Princípio da Inversão de Dependência

Dependency Inversion Principle — DIP

Ricardo Dias
Contexto Delimitado
9 min readFeb 17, 2020

--

Este artifício de arquitetura foi publicado inicialmente no artigo OO Design Quality Metrics (MARTIN 1994) e definido como princípio na revista C++ Report (MARTIN 1996). Basicamente, se os princípios OCP e LSP forem cumpridos corretamente, o DIP também estará sendo cumprido.

Atenção: não confunda “Inversão de Dependência” com “Injeção de Dependências”, pois são coisas diferentes!! Enquanto o primeiro é um “Princípio de Arquitetura”, o segundo é um padrão de design (Design Pattern). Neste artigo estamos falando da Inversão, ou seja, do princípio criado por Martin!

Enquanto OCP indica o objetivo da arquitetura Orientada a Objetos, o DIP indica o mecanismo principal. A inversão de dependência é a estratégia de depender de interfaces ou classes abstratas, ao invés de classes concretas.

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. (MARTIN 1996)

Para entender as afirmações de Martin, vamos tentar exemplificar uma situação prática. Mas antes, é importante descobrir o que significam alguns termos:

  • Módulos de alto nível: são as rotinas mais fáceis de entender, mais próximas da realidade de qualquer programador. Tendem a exigir menos carga mental para serem usadas;
  • Módulos de baixo nível: são as rotinas mais complexas e difíceis de entender. Geralmente são compostas de implementações de cálculos ou comportamentos específicos.
  • Detalhes: são os artefatos que não deveriam fazem parte da arquitetura de forma acoplada, mas que, no entanto, são necessários para um sistema funcionar. O acesso ao banco de dados, por exemplo, é um detalhe; assim como o gerenciamento de arquivos.

No diagrama abaixo, pode-se observar uma estrutura altamente acoplada, que segue uma realidade comum em projetos desenvolvidos com paradigma procedural:

Estrutura de dependências em um paradigma procedural

O esquema acima mostra uma cascata clássica, oriunda da natureza própria de projetos procedurais (que não são orientados a objetos). Para entender melhor, observe o código PHP abaixo:

function gerarNotaFiscal(int $pedidoId)
{
$preçoPedido = calcular($pedidoId);
// rotina para gerar a nota fiscal
}
function calcular(int $pedidoId) : float
{
$produtos = obterProdutos($pedidoId);
// rotina que calcula a soma de todos os produtos
return 678.50;
}
function obterProdutos(int $pedidoId) : array
{
// rotina que busca a lista de produtos
// no banco de dados com base no id do pedido
return [];
}

No exemplo, as dependências seguem uma sequência, onde o fluxo começa com a função de alto nível (gerarNotaFiscal), que depende de uma função de baixo nível (calcular) que, por sua vez, depende de um detalhe (obterProdutos).

Uma implementação como essa é, naturalmente, acoplada. Isso é um dos presentes inclusos em linguagens de programação procedural.

Agora imaginemos um desenvolvedor que trabalhe com um paradigma procedural e esteja aprendendo o paradigma de orientação a objetos. Ao se deparar com a realidade acima, ele facilmente transformaria a implementação, deixando-a como no exemplo abaixo:

Estrutura de dependências em um paradigma OO

Só de olhar esse diagrama, comparando-o com o modelo procedural anterior, percebemos que nada mudou a nível de arquitetura. As funções foram transformadas em métodos dentro de classes e nada mais:

class NotaFiscal
{
public function gerarNota(int $pedidoId)
{
$calculo = new Calculo;
$preçoPedido = $calculo->calcularPreçoPedido();
// rotina para gerar a nota fiscal
}
}
class Calculo
{
public function calcularPreçoPedido(int $pedidoId) : void
{
$produtos = new Produtos;
$lista = $produtos->obterProdutos($pedidoId);
// rotina que calcula a soma de todos os produtos
return 678.50;
}
}
class Produtos
{
public function obterProdutos(int $pedidoId) : array
{
// rotina que busca a lista de produtos
// no banco de dados com base no id do pedido
return [];
}
}

As dependências continuam seguindo a mesma sequência, começando com a classe de alto nível (NotaFiscal), que depende de uma classe de baixo nível (Calculo) que, por sua vez, depende de um detalhe (Produtos).

Qualquer mudança em qualquer uma dessas classes irá quebrar todas as que estiverem ligadas a partir das dependências provenientes do acoplamento.

Se quebrou, vai ter que consertar! Não importa a dificuldade.

Controlando o acoplamento

De maneira geral, será impossível criar uma aplicação onde a arquitetura seja totalmente desacoplada e abstrata, pois acoplamentos concretos sempre existirão!

Segundo ANICHE (1998), o segredo está em saber diferenciar os acoplamentos ruins dos acoplamentos bons, pois assim “modelaremos nossos sistemas fugindo dos ‘acoplamentos perigosos’ ”.

“Toleramos as dependências concretas porque sabemos e confiamos que elas não mudarão”. (MARTIN 2019)

Um exemplo de tolerância coerente é quando usamos como base algum framework ou, ainda, quando fazemos uso de funcionalidades contidas na própria linguagem de programação utilizada. Nesses casos, geralmente não será produtivo reinventar a roda a fim de criar abstrações desnecessárias. O consumo das funcionalidades existentes nas classes concretas será o melhor caminho.

Controlando as dependências voláteis

Quando não conseguimos ter uma noção sobre o futuro de uma classe, podemos considerá-la como uma classe volátil.

Os elementos voláteis são, geralmente, as rotinas ou algoritmos que estão em processo de desenvolvimento e que, por sua natureza, sofrem mudanças frequentes. É preciso cuidado e parcimônia ao criar dependências com esses tipos de implementações.

Deve-se “evitar depender de elementos voláteis do nosso sistema.” (MARTIN 2019)

Na dúvida sobre como tratar a volatilidade das dependências, lembre-se sempre que se “uma classe for depender de outra, ela deve depender sempre de outro módulo mais estável do que ela mesma” (ANICHE 1998).

Em outras palavras, “se A depender de B, a ideia é que B seja mais estável que A. Isso significa que “suas classes devem sempre andar em direção à estabilidade”, ou seja, elas devem depender de módulos mais estáveis do que ela própria (ANICHE 1998).

Invertendo as dependências

Como dito no início deste artigo, para atingir o objetivo proposto pela arquitetura orientada a objetos, é preciso um mecanismo. Essa é a missão do Princípio da Inversão de Dependência.

De acordo com BOOCH (1996), “… todas as arquiteturas orientadas a objetos e bem estruturadas … possuem camadas claramente definidas, onde cada camada fornece algum conjunto coerente de serviços … por meio de uma interface bem definida e controlada.

As classes não devem ser acopladas a outras classes concretas ou a classes que possam ser instanciadas (como vimos nos exemplos anteriores). Em vez disso, “classes devem ser acopladas a outras classes base ou abstratas” (SILVA 2013). Se o acoplamento é inevitável, que seja por meio de abstração.

“Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.” (MARTIN 1996)

“Os sistemas mais flexíveis são aqueles em que as dependências de código-fonte se referem apenas a abstrações e não a itens concretos.” (MARTIN 2019)

Para adequar o exemplo anterior ao DIP, vamos inverter as dependências. Ao invés de depender de classes concretas vamos fazer as classes dependerem de abstrações:

Dependência invertida para uma estrutura mais estável.

No diagrama acima a direção das cetas indica a dependência das classes. Note que a ceta vermelha está invertida, quebrando a cascata. Agora, todas as classes dependem de abstrações (ICalculoPedidos e IProdutos).

class NotaFiscal
{
public function gerarNota(ICalculo $calculo)
{
$preçoPedido = $calculo->total();
// rotina para gerar a nota fiscal
}
}
interface ICalculo
{
public function calcularPreçoPedido(IProdutos $produtos) : void;
public function total() : float;
}
class Calculo implements ICalculo
{
private $produtos;
public function calcularPreçoPedido(IProdutos $produtos) : void
{
$this->produtos = $produtos;
}
public function total() : float
{
$lista = $this->produtos->obterProdutos();
// rotina que soma os preços dos produtos ...
return 678.50;
}
}
interface IProdutos
{
public function setarPedidoId(int $pedidoId) : void;
public function obterProdutos() : array;
}
class Produtos implements IProdutos
{
private $pedidoId;
public function setarPedidoId(int $pedidoId) : void
{
$this->pedidoId = $pedidoId;
}
public function obterProdutos() : array
{
return $this->consultarNoBanco($this->pedidoId);
}
private function consultarNoBanco(int $pedidoId) : array
{
// implementação qualquer ...
return [];
}
}
$produtos = new Produtos;
$produtos->setarPedidoId(55);
$calculo = new Calculo;
$calculo->calcularPreçoPedido($produtos);
$preço = new NotaFiscal;
$preço->gerarNota($calculo);

“Esse conceito, de acoplamento abstrato, é o meio pelo qual o LSP alcança sua flexibilidade, mecanismo essencial ao DIP, e é o coração do OCP.” Em outras palavras, o DIP é o “resultado estrutural do uso rigoroso do OCP e LSP” (SILVA 2013).

É claro que essa ideia não deve ser tratada como uma regra absoluta. Haverão casos onde será necessário “tolerar” a dependência de uma funcionalidade concreta. O DIP deve ser seguido sempre que possível e quando seu uso for coerente.

Práticas e cuidados ao programar

Martin resume o Princípio da Inversão de Dependência, enumerando algumas práticas de programação bem específicas (MARTIN 2019), a fim de facilitar a identificação das diversas situações o desafio de arquitetar uma aplicação:

  • Não se refira a classes concretas voláteis: deve-se evitar o uso de classes voláteis na especificação de tipos em argumentos ou de retorno em métodos. Nesses casos, é preferível usar uma interface abstrata ao invés de referenciar diretamente a classe volátil. Leia o artigo O Princípio da Substituição de Liskov para mais informações sobre o uso correto de tipos;
  • Não derive de classes concretas voláteis: complementando a regra anterior, Martin explica que a herança deve ser usada com muito cuidado. Classes voláteis, se herdadas, propagarão erros ou alterações para seus dependentes e isso é tudo o que desejamos evitar;
  • Não sobrescreva métodos concretos: podem existir dependências de código-fonte em métodos concretos. Portanto, ao sobrescrever esses métodos (fazer override), essas dependências não serão eliminadas mas continuarão presentes após o override, servindo apenas como lixo.

Conclusão

O Princípio da Inversão de Dependência é o ponto ápice do SOLID. Após dominar todos os princípios anteriores e colocá-los em prática, o DIP servirá como uma luva na maioria dos casos.

Implementar um bom software não é uma tarefa fácil, pois exige muito treino e, principalmente, aprendizado com os próprios erros e acertos. Aí se encontra a mágica: não existe mágica!

O que difere os “apertadores de teclas” dos artesãos de código-fonte é justamente a busca pela excelência e conhecimento. Qualquer um pode apertar teclas e colocar caracteres um na frente do outro, mas dar sentido e clareza a essa sequencia de caracteres é como uma sinfonia: exige disciplina, persistência e principalmente paixão.

É muito mais do que saber essa ou aquela linguagem de programação, é preciso se aprofundar na arte de dar sentido, com a humildade e a consciência de saber que fazemos parte do vasto universo de conhecimento e que, na verdade, o que sabemos ainda não é nada perto do que ainda vamos aprender.

Espero que esta série tenha sido e continue sendo útil para você. Até a próxima!

Leia todos os artigos desta série:

Referências para Aprofundamento

ANICHE, Maurício. Orientação a Objetos e SOLID para ninjas. São Paulo, SP, Brasil. Casa do Código, 1998.

GAMMA, Erich; HELM, Richard; JONSON, Ralph; VLISSIDES, John. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995.

BOOCH, Grady. Object Solutions, Addison Wesley, 1996;

MARTIN, Robert C. Design Principles and Design Patterns. Disponível em <https://fi.ort.edu.uy/innovaportal/file/2032/1/design_principles.pdf>. Acesso em 03/08/2019.

MARTIN, Robert C. OO Design Quality Metrics. 1994. Disponível em <https://linux.ime.usp.br/~joaomm/mac499/arquivos/referencias/oodmetrics.pdf>. Acesso em 01/09/2019

MARTIN, Robert C. The Dependency Inversion Principle. 1996. Disponível em <http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod>. Acesso em 01/09/2019

MARTIN, Robert C. Princípios, Padrões e Práticas Ágeis em C#. Bookman, Porto Alegre, RS, 2011.

MARTIN, Robert C. Arquitetura Limpa: O guia do artesão para estrutura e design de software. Alta Books Editora, Rio de Janeiro, RJ, 2019.

SILVA, Álvaro Pereira da. Princípios de design aplicados na melhoria de código-fonte em sistemas orientados a objetos, UFLA, Lavras, MG, 2013.

--

--

Ricardo Dias
Contexto Delimitado

Apaixonado por padrões, programação clara, elegante e principalmente manutenível. Trabalha como desenvolvedor deste 2000, incrementando a cada ano este loop…