Introdução

Sempre ouvimos dizer que no nosso código precisamos buscar diminuir o acoplamento e aumentar a coesão. Pra alcançar esse fim existem princípios que guiam o desenvolvedor na hora da criação do código. Esses princípios são bastante explorados em diversos posts — o famoso S.O.L.I.D. — e hoje eu trago um pequeno resumo da Inversão de Dependência, o D desse acrônimo.

A principal consequência de aplicar a técnica de Inversão de Dependência é a obtenção de um código mais desacoplado e mais facilmente testável. A seguir, busco explicar os motivos.

O contexto

A figura abaixo é retirada do livro Arquitetura Limpa, onde o autor introduz o tema pela primeira vez.

Diagrama em árvore com o módulo Main no topo, ligado a módulos HL, ML e LL. Setas vermelhas contínuas descem indicando dependência de código, e setas verdes tracejadas indicam o fluxo de controle — ambas no mesmo sentido.
Figura 5.1 — Source code dependencies versus flow of control. Reproduzida de MARTIN, Robert C. Clean Architecture (Prentice Hall, 2017).
  • MAINFunção principal
  • HLHigh-level function
  • MLMid-level function
  • LLLow-level function
  • Seta verde tracejadaControle de fluxo
  • Seta vermelha contínuaDependência de código

“Beleza, entendi a imagem. Mas o que especificamente quer dizer controle de fluxo e dependência?”

Você pode se perguntar. E eu resumo assim:

Controle de fluxo

É a ordem na qual as instruções são chamadas. Pode-se dizer que o fluxo é o caminho que a thread de execução percorre — é o caminho que a execução do programa segue, determinado por loops, estruturas de decisão, chamadas de funções e outros mecanismos de controle de fluxo.

Dependência de código

Diz-se que há dependência entre códigos quando há a necessidade de importação de um código em outro. No exemplo acima, o módulo MAIN precisa ter uma referência de HL (precisa importar HL) para funcionar.

Na figura acima, temos que o fluxo do código está atrelado às dependências, ou seja, MAIN só pode chamar uma função de HL (ou seja, direcionar o controle de fluxo) pois existe uma importação de HL em MAIN.

Isso gera uma dependência, de forma que MAIN depende de HL para funcionar. Essa dependência quebra o princípio de Inversão de Dependência, que diz que módulos de alto nível não devem depender de módulos de baixo nível — ambos devem depender de abstrações (interfaces).

Dependendo de interfaces

Interfaces funcionam como um contrato entre os envolvidos. Uma classe (ou uma função, como veremos) que implementa uma interface significa que aquele que implementa deve possuir todos os métodos e atributos que a interface possui.

Por exemplo:

Diagrama: o módulo HL1 aponta com uma seta vermelha simples para a interface I, que declara o método F(). O módulo ML1, que também declara F(), aponta para I com uma seta de diamante aberto. Uma seta verde tracejada vai de HL1 para ML1.
Figura 5.2 — Dependency inversion. Reproduzida de MARTIN, Robert C. Clean Architecture (Prentice Hall, 2017).
  • Seta vermelha simples (HL1 → I)Dependência de USO
  • Seta vermelha de diamante aberto (ML1 → I)Dependência de IMPLEMENTAÇÃO
  • Seta verde tracejadaControle de fluxo

Aqui, vale notar que as setas vermelhas são diferentes:

Por isso é tão importante que a função que implementa uma interface implemente todos os métodos dela — outras funções esperam que isso aconteça (além disso, o TypeScript vai gritar com você se não o fizer).

Mas onde está a inversão, afinal?

Olhe novamente para a imagem acima, onde temos a interface. Nesse momento, a inversão de dependência já aconteceu. Podemos reorganizar essa imagem da seguinte forma, para melhor visualização:

Diagrama redesenhado: HL1 à esquerda e ML1 à direita, ambos com setas vermelhas tracejadas apontando para dentro, em direção à interface I no centro. Abaixo, uma seta verde tracejada vai de HL1 até ML1, no sentido oposto às dependências.
A mesma relação, reorganizada: as dependências apontam para o centro, o fluxo de controle atravessa na direção oposta.

Como durante o runtime a interface não existe, HL1 não depende da interface — ele a usa. Durante o runtime, HL1 simplesmente chama a função F() disponível em ML1 sem precisar importar ML1 diretamente.

Perceba que o controle de fluxo e a dependência de código (ML1 e I) estão apontando para sentidos opostos, caracterizando uma inversão de dependência.

Como isso fica na prática?

Enquanto eu estava revisando esse assunto, encontrei uma excelente e bem pequena explicação prática sobre Inversão de Dependência com TypeScript. Então, ao invés de criar minha versão me inspirando (copiando 😅), acho mais interessante compartilhar a fonte aqui direto.

Esse artigo do Alex Nault tem um exemplo bem bacana, simples e sucinto sobre inversão com TypeScript. Acredito que o conteúdo desse meu artigo complementa muito o dele, e vice-versa.

Em seu artigo, Alex demonstra uma implementação sem DIP e depois demonstra como aplicar DIP no mesmo contexto. Além disso, a explicação dele sobre como essa prática aumenta a testabilidade é muito boa.

Próximos passos

Se você leu o artigo do Alex Nault, percebeu que ele mostra as entidades envolvidas, mas não coloca os relacionamentos entre elas. Por exemplo:

Três caixas soltas, sem setas entre elas: SignupService à esquerda, a interface ApiClient no alto à direita e HttpClient abaixo dela.
As entidades do exemplo, sem os relacionamentos desenhados.

Aqui, o autor apenas mostrou as entidades com as quais vai trabalhar, mas não estabeleceu graficamente os relacionamentos.

Desafio

Pegue papel e caneta (ou o Excalidraw), replique essas entidades e tente aplicar os relacionamentos. De onde sai e para onde aponta o fluxo de controle? E a seta que indica uso? E a seta que indica implementação?

Pode parecer bobo, mas acredito que isso pode te ajudar a consolidar o conceito.

Referências

  1. MARTIN, Robert C. Clean Architecture: A craftsman's guide to software structure and design. Prentice Hall, 2017.
  2. MENDES, Antonio. Arquitetura de Software: Desenvolvimento orientado para arquitetura. Editora Campus, 2002.
  3. NAULT, Alex. Dependency inversion principle in functional TypeScript. Disponível em: alexnault.dev/dependency-inversion-principle-in-functional-typescript. Acesso em: 2 ago. 2024.

Precisa de ajuda com a arquitetura do seu projeto?

Desenvolvo sites e produtos digitais com foco em código sustentável. Vamos conversar sobre o seu.

Falar comigo