O princípio da inversão de dependência

O princípio de Inversão de Dependência é uma das técnicas fundamentais para a aplicação do TDD (Test Driven Development), um dos conceitos mais utilizados e requeridos no mercado de desenvolvimento de software atualmente. Este princípio é um dos presentes no SOLID

Vamos ver em detalhes qual o objetivo deste princípio, para logo mais aplica-lo em um exemplo de forma prática.

Dependency Inversion Principle (DIP)

O princípio de Inversão de Dependência corresponde a letra D entre os princípios SOLI ‘D’
Seu princípio começa com esta afirmação.

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

Referência de forma direta:

Antes de irmos mais afundo sobre esta afirmação, gostaria de deixar aqui bem claro que um principio não é uma lei ou regra, é apenas um conselho que pode ser seguido ou não.

Este principio nos diz que sistemas mais flexiveis são aqueles que possuem dependências de código fonte que apontam para abstrações e nunca para coisas concretas, ou seja dependem sempre de interfaces.

Referência de forma abstrata:

Bom, mas o que seriam essas coisas abstratas ?

Um código bem organizado sempre tem uma hierarquia. Há módulos de alto nível e módulos de baixo nível. Mas, às vezes, desenvolvedores iniciantes não entendem esse conceito e trazem diretamente módulos de baixo nível para módulos de alto nível.

Trazendo para uma linguagem de programação tradicional orientada a objetos como Java, talvez fique um pouco mais simples de se explicar no formato de código.

Vamos mostrar essa violação na prática logo abaixo.

Você pode ver o que há de errado com este código?

Aqui está talvez um exemplo mais comum, bastante ultilizado atualmente, onde podemos ver bem claro essa violação.

import java.util.Arrays;
import java.util.List;

// Classe do módulo de alto nível
class BookCatalog {
    public void listAllBooks() {
        SQLBookRepository sqlBookRepository = new SQLBookRepository();
        List<String> allBookNames = sqlBookRepository.getAllBookNames();
        // Exibe os nomes dos livros
    }
}
// Classe do módulo de baixo nível 
class SQLBookRepository {
    public List<String> getAllBookNames() {
        return Arrays.asList("Clean Architecture", 
        "Domain-Driven Design", "Working Effectively with Legacy Code");
    }
}

Você consegue ver o problema acima? BookCatalog é uma classe de um módulo de alto nível, mas depende diretamente de uma classe de baixo nivel SQLBookRepository.

Aqui você já está vendo a violação do Princípio de Inversão de Dependência porque o módulo de alto nível BookCatalog depende de uma classe de baixo nivel a classe SQLBookRepository.

Essa dependência com certeza nos traria problemas para aplicar o conceito de TDD.

Agora que você consegue visualizar o problema, como poderiamos consertar isso? 

Antes de corrigi-lo, vamos ver o que o conceito de abstração significa em design de software.

Abstração

O conceito de abstração consiste em esconder os detalhes de algo, no caso que estamos analisando, os detalhes da implementação. Eles são desnecessários.

No mundo real, utilizamos abstrações o tempo todo. Tudo que não sabemos como funciona em detalhes pode ser considerado uma abstração.

Vamos ver um exemplo prático abaixo.

Código sem abstração:

class Ferrari { 
    public void drive() { 
    } 
} 
class CarUtil { 
    public static void drive(Ferrari ferrari) { 
        ferrari.drive(); 
    } 
}

Como você pode ver no código acima, o método estático da classe CarUtil depende da classe Ferrari. Para que o método drive() funcione você deve fornecer uma instância de Ferrari.

Em design de software isto é chamado de acoplamento forte. Isso significa também que, quando você altera o método drive() dentro da classe Ferrari, a classe CarUtil é diretamente afetada. Este é um cenário muito propenso para gerar bugs.

 Código com abstração
 

interface Car {
    public void drive();
}
class Ferrari implements Car {
    @Override
    public void drive() {
    }
}
class CarUtil {
    public static void drive(Car car) {
        car.drive();
    }
}

Este código parece melhor. O método estático da classe CarUtil não depende da classe Ferrari, mas depende da interface Car. Assim ele pode receber qualquer argumento que implemente a interface Car. Aqui está o que chamamos de abstração.

Agora, vamos voltar ao exemplo de código anterior.

Refatorando código anterior com abstração

import java.util.Arrays; 
import java.util.List; 

// Classe do módulo de alto nível
class BookCatalog { 
   private BookRepository bookRepository; 
   public BookCatalog(BookRepository bookRepository) { 
        this.bookRepository = bookRepository; 
    } 
    public void listAllBooks() { 
        // Módulo de alto nível depende da abstração agora 
        List<String> allBookNames = bookRepository.getAllBookNames(); 
        // Exibe os nomes dos livros
    } 
}

interface BookRepository { 
    List<> getAllBookNames(); 
} 

// Classe do módulo de baixo nível 
class SQLBookRepository implements BookRepository {
    public List<String> getAllBookNames() {
        return Arrays.asList("Clean Architecture", 
        "Domain-Driven Design", "Working Effectively with Legacy Code");
    }
}

Agora, BookCatalog depende de  BookRepository em vez de depender de SQLBookRepository diretamente. Também podemos ver que agora é possivel “injetar” BookRepository no construtor da classe BookCatalog resolvendo e muito o nosso problema se fôssemos aplicar aqui o TDD.

Estrutura antes da refatoração:

Estrutura após a refatoração:

Vantagens obtidas após a refatoração

  • Aplicamos o Repository Pattern: Conseguimos isolar o acesso ao banco de dados abstraindo a camada de persistência. Independentemente da sua origem deixamos o mecanismo de como os dados são acessados isolados das regras de negócio. Seguindo esse pensamento você não precisa saber qual banco de dados vai usar, pode não ser especificamente somente um banco de dados relacional, mas também um em NoSQL.

  • Prática do acoplamento fraco : A operação BookCatalog.listAllBooks() não depende de um objeto específico. Isso significa que, quando você altera o código em SQLBookRepositoryBookCatalog não é afetado diretamente, isso se chama Loose coupling (Acoplamento fraco) uma prática essencial para sistemas flexíveis.

Conclusão

  • 1 – Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.

  • 2 – As abstrações não devem depender de detalhes. Os detalhes devem depender de abstrações.

Se você consegue entender essas duas declarações, você entendeu completamente o princípio da Inversão de Dependência.

Esse principio é fundamental para um sistema flexível e compatível com mudanças além de ser essencial para a aplicação do TDD, aliás falaremos mais sobre este assunto nos próximos posts.

You May Also Like