SOLID — Dependency Inversion Principle (DIP)
Pra mim, um dos princípios mais legais e também o último da nossa série de artigos sobre SOLID, hoje vamos conhecer um pouco mais sobre o DIP (Dependency Inversion Principle ou em português, Princípio da Inversão de Dependência). Na teoria ele é bem simples, basicamente nos diz que devemos sempre depender de abstrações e não de implementações.
Em suas escritas, Uncle Bob diz que este princípio pode ser definido da seguinte forma:
1. Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender da abstração.
2. Abstrações não devem depender de detalhes. Detalhes devem depender de abstrações.
Inversão de Dependência x Injeção de Dependência
Ao trabalhar com a programação orientada a objetos, é muito comum ouvirmos o termo “Injeção de Dependência”. Você mesmo deve estar se perguntando qual a diferença entre eles. Vale ressaltar que, ambos são coisas distintas, mas que relacionam entre si com um proposito em comum: deixar o código desacoplado e de fácil manutenção.
Nota: Inversão de dependência, é um princípio (conceito) e a Injeção de dependência é um padrão de projeto (Design Pattern).
Vamos entender tudo isso na prática através de alguns exemplos:
Se você estiver lembrado, no artigo anterior onde nos aprendemos sobre o Interface Segregation Principle, criamos um projeto fictício para cadastro de aves em um determinado zoológico. Dando sequência a este projeto, vamos agora pensar na sua principal função, que é de fato, efetuar o cadastro de aves.
Uma premissa para que o cadastro seja efetuado, é estabelecer uma conexão com um banco de dados, por tanto, o método Salvar
vai criar uma instância da classe TFirebirdConnection
para realizar as operações.
Se você acompanhou todos os artigos da série sobre SOLID até o momento, acredito que você já tenha percebido que nesse pequeno trecho de código temos um alto nível de acoplamento. Isso acontece porque a classe TPinguim
tem a responsabilidade de criar uma instância da classe TFirebirdConnection.
Se quiséssemos reaproveitar essa classe em outro sistema, teríamos que obrigatoriamente levar a classe TFirebirdConnection
junto, portanto, temos um forte acoplamento aqui. Para resolver esse problema, podemos refatorar nosso código da seguinte forma:
Com o código refatorado, a criação do objeto TFirebirdConnection
deixa de ser uma responsabilidade da classe TPinguim.
A classe de conexão com o banco de dados virou uma dependência que deve ser injetada no método Salvar. Isso mesmo, injetada. Nesta pequena refatoração, aplicamos o padrão de projeto de Injeção de dependência.
Apesar de termos usado a injeção de dependência para melhorar o nível de acoplamento do nosso código, ele continua violando o princípio da inversão de dependência (lembre-se, um não é igual ao outro).
Além de violar o DIP, se você prestar atenção na forma que o exemplo foi codificado irá perceber que ele também viola o Open-Closed Principle. Por exemplo, se precisarmos alterar o banco de dados de Firebird para Postgres teríamos que editar a classe TPinguim.
Aplicando o DIP
Você deve estar se perguntando porque que ainda nosso projeto viola o princípio da inversão de dependência. A resposta é porque ainda estamos dependendo de uma implementação e não de uma abstração, simples assim.
Assim como nós já vimos, a definição do DIP diz que, um módulo de alto nível não deve depender de módulos de baixo nível, ambos devem depender da abstração. Então, a primeira coisa que precisamos fazer é identificar no nosso código qual é o módulo de alto nível e qual é o módulo de baixo nível. Módulo de alto nível é um módulo que depende de outros módulos.
No nosso exemplo, TPinguim
depende da classe TFirebirdConnection.
Sendo assim, TPinguim
é o módulo de alto nível e TFirebirdConnection
é o módulo de baixo nível. Acontece que, TFirebirdConnection
é uma implementação e não uma abstração.
Refatorando a classe TFirebirdConnection:
É muito comum você achar em cursos e livros de orientação a objetos, menções orientando para que você “programe para uma interface e não para uma implementação”. Pois bem, é exatamente o que iremos fazer agora, criar uma interface para a nossa classe TFirebirdConnection.
Pronto! Agora a nossa classe TPinguim
não tem a mínima ideia de qual banco de dados que a aplicação irá utilizar. Dessa forma, não estamos mais violando o princípio de inversão de dependência e ambas as classes estão desacopladas, dependendo apenas de uma abstração. Além disso, estamos permitindo a reusabilidade do código e também respeitando o SRP e o OCP.
Refletir
Você soldaria uma lâmpada diretamente a fiação elétrica de uma parede?