Desvendando o SOLID: Dependency Inversion Principle

Vitor Ferraz Varela
6 min readAug 18, 2021

--

Introdução

Ao longo dessa série de artigos, passamos por cada letra do acrônimo do SOLID e finalmente chegamos à última letra. Hoje vamos falar sobre o princípio de inversão de dependência e como ele é um resultado da aplicação dos outros princípios. Mas caso você não visto os artigos anteriores sobre os demais princípios do SOLID, vou deixar os links aqui:

Desvendando o SOLID: Single Responsibility Principle

Desvendando o SOLID: Open/closed Principle

Desvendando o SOLID: Liskov Substitution Principle

Desvendando o SOLID: Interface Segregation Principle

A inversão de dependência aborda como criamos abstrações que não deixem nosso código frágil a mudanças, acoplado sem necessidade e que dificulte o reuso de código. Isso tudo afeta diretamente a qualidade do nosso código, evolução e manutenção.

DIP — Inversão de dependência

Primeiramente acho que podemos entender qual é a definição mais formal sobre esse princípio.

De acordo com Uncle Bob, este princípio 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.
  • Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.

Nos últimos artigos falamos muito sobre a coesão e como é fundamental para manutenção e reuso do nosso código. Além disso, abordamos sobre como devemos lidar com heranças e protocolos para ter um código bem estruturado, que pode ser aberto para extensão sem necessidade de modificação.

Muitos desses princípios acabam se relacionando, assim como a inversão de dependência que em suma se baseia na forma que criamos componentes reutilizáveis, desacoplando dependências e abstraindo camadas.

Uma reflexão sobre a arquitetura do nosso software

Durante o período de desenvolvimento tomamos decisões sobre a arquitetura que no futuro podem acabar afetando a manutenção e adição de novas funcionalidades.

Pensando nisso, alguns pontos são muito importantes de considerar, como por exemplo a rigidez do software ou seja a dificuldade que é alterá-lo sem criar efeitos colaterais, ou o acoplamento que nosso código possui, que dificulta a reutilização de componentes ou até mesmo extrair um pedaço de código sem que seja necessário fazer uma grande refatoração.

  • Fragilidade: Quando temos a extensão de funcionalidade causando mudanças no nosso sistema ou diversas parte dele causando efeitos colaterais.
  • Acoplamento: Além da quebra do princípio do aberto e fechado, quando dependemos de classes concretas e não abstrações, quando é feito alguma alteração isso tem um efeito direto nas classes que dependem da classe concreta, além disso temos um código acoplado e assim difícil de ser reutilizável pois essas estruturas dependentes de outras.

O princípio de inversão de dependência conversa muito com esses quesitos e desta forma conseguimos ter uma visão clara da definição do DIP e como se relaciona com uma boa arquitetura. Como resultado temos a criação de componentes reutilizáveis, desacoplando dependências, baseando-se em abstrações, sem rigidez e com uma arquitetura limpa.

Exemplo prático

Para o nosso exemplo prático, iremos analisar uma camada de network bem simples. Alguns detalhes de implementação sobre como criar uma URLRequest, foram omitidos com fins de deixar a explicação mais sucinta e desta forma pode ser possível se concentrar em identificar os problemas e que efeitos um módulo de baixo nível causa em um módulo de alto nível quando não respeitamos a inversão de dependência. Vale lembrar que uma camada de network deve ser mais robusta do que esse exemplo, pensando em testabilidade e requisitos de segurança, mas caso tenha curiosidade vou deixar o github com o projeto.

O nosso exemplo consiste em consumir uma API pública e mostrar o resultado da chamada para o usuário. Primeiro vamos deixar tudo configurado para essa chamada, criando nossos objetos de modelo e o que é necessário realizar a chamada da API.

Agora vamos dar uma olhada no nosso módulo de baixo nível, que é a camada de network:

Essa módulo de baixo nível será usado na nossa ViewModel, que será o módulo alto nível:

Qual o problema do nosso código?

A primeira coisa que acho importante ressaltar nesse exemplo, é que não importa se é um código novo ou legado, nesse caso por exemplo usamos funcionalidades novas do swift como async/await e generics para criar uma camada de network, e mesmo assim estamos quebrando o DIP, o real problema está no design do código.

Desta forma, sempre que pegar um código legado para dar manutenção ou criar uma funcionalidade nova, sempre reflita sobre o design do código, assim podemos resolver vários problemas de manutenção e escalabilidade do nosso projeto.

Agora voltando para nosso exemplo, vamos entender a Fragilidade do nosso código:

  • Se precisássemos mudar algo na nossa camada de network, nosso módulo de alto nível pode sofrer algum impacto pois ele depende de uma classe concreta e não uma abstração.

Imagine que o time de negócio da empresa queira utilizar nossa aplicação em outro time, mas agora ele precisa que seja usando o Moya ao invés do URLSession e outro time da empresa queira usar ao invés de REST o Graphql.

Podemos pensar da mesma forma para uma camada de persistência que use o CoreData, mas e se outro time quiser usar Realm? De que forma podemos escrever nosso código sem que ele seja acoplado e dependente dos detalhes de implementação?

Criando abstrações

Para seguir o princípio de inversão de dependências devemos fazer com que nossos módulos de alto nível e de baixo nível dependam de abstrações.

Para o nosso exemplo iremos criar um protocolo que define a assinatura da nossa chamada para API:

Assim nossa camada de network usando o URLSession ficará desta forma:

E nossa ViewModel:

Pode parecer uma mudança pequena, mas desta forma conseguimos desacoplar nossa ViewModel de uma implementação concreta. Se um dia quisermos parar de usar o URLSession e usar outra estratégia ou framework, podemos fazer isso de forma mais transparente sem quebrar nossas classes visto que elas dependem de um protocolo, uma abstração.

Criar um protocolo é uma das formas que podemos usar para criar abstrações, existem outras estratégias que podemos usar para conseguir o mesmo resultado. Podemos usar alguns design patterns como o Facade ou o Adapter que são usados para criar camadas de abstrações.

Muitos dos design patterns que usamos são fortemente baseados em criar protocolos que servem para abstrair camadas e desacoplar nosso código.

Outro ponto importante é que nem sempre precisamos criar protocolos para todos os objetos do nosso projeto. Podemos criar abstrações de objetos sem a necessidade de criar um protocolo. No nosso exemplo fazemos isso quando ao invés de retornar um protocolo do nosso objeto de retorno da API, retornando um objeto genérico.

Além desses benefícios, quando respeitamos o DIP e consequentemente os outros princípios do SOLID, conseguimos melhorar em muito a testabilidade do nosso código, principalmente pois agora não dependemos de implementações concretas que podem possuir muitas dependências que podem atrapalhar na hora criamos testes.

Além desses benefícios, quando respeitamos o DIP e consequentimente os outros princípios do SOLID, conseguimos melhorar em muito a testabilidade do nosso código, principalmente pois agora não dependemos de implementações concretas que podem possuir muitas dependências que podem atrapalhar na hora criamos testes.

Conclusão

Hoje abordamos a última letra do SOLID, que fala sobre o princípio da inversão de dependência. Esse princípio fala sobre algo que já vínhamos abordando nos últimos artigos que é sobre as abstrações que criamos, mais especificamente que módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações. Além disso, vimos quais os impactos que não seguir esse princípio causa no nosso código em termos de acoplamento e fragilidade, afetando assim nosso design de código.

Espero que tenham gostado dessa série Desvendando o SOLID. Caso você não tenha visto os outros artigos, vale a pena dar uma olhada:

Desvendando o SOLID: Single Responsibility Principle

Desvendando o SOLID: Open/closed Principle

Desvendando o SOLID: Liskov Substitution Principle

Desvendando o SOLID: Interface Segregation Principle

Muito obrigado por me acompanhar até aqui e me siga no Medium para não perder os próximos artigos.

Vocês podem me encontrar nas redes sociais:

Linkedin | Instagram | Twitter

--

--