Desmistificando o conceito de inversão de controle e sua relação com a injeção de dependência

O acoplamento fraco entre unidades distintas de um sistema já faz parte do bom senso de um bom desenvolvedor. Sabemos que é muito mais fácil lidar com o conhecimento pontual de uma correção do que a necessidade de conhecimento de uma vasta hierarquia de relacionamento entre unidades e, uma das técnicas que promove o tão procurado sistema flexível de baixo acoplamento é a inversão de controle.

A inversão de controle é a técnica que inverte o paradigma de uma programação tradicional. Com a aplicação dessa técnica, o que antes era feito de forma direta é agora delegado para uma infraestrutura dedicada que muitas vezes é também conhecida como container. Em geral, e de forma menos abstrata, o que temos é que a instanciação das dependências de uma determinada classe se dará fora dela. Vejamos um exemplo prático de como isso deve acontecer:

Imagine a sintonização de um canal através de uma antena comum conforme o exemplo abaixo:

public class Televisao {

  public void sintonizar canal(final int canal){
    Sintonizador sintonizador = new SintonizadorPorAntena();
    sintonizador.sintonizar(canal) ;
  }

}

Se a primeira vista não foi possível observar qualquer problema nesse design então suponha que agora você queira que a sintonização da sua TV seja realizada por um aparelho a cabo e não mais por antena. Acontece que, pelo exemplo anterior, será necessário realizar uma manutenção na sua TV que a principio não tinha nenhum problema, ou seja, uma unidade externa absolutamente independente do seu aparelho exigiu uma manutenção nele. Sendo um um pouco mais claro, tal mudança exigiria algo como a seguinte manutenção:

public class Televisao {

  public void sintonizar canal(final int canal){
    Sintonizador sintonizador = new SintonizadorPorCabo();
    sintonizador.sintonizar(canal) ;
  }

}

Em outras palavras, toda vez que houver a necessidade de se alterar o sintonizador de uma televisão, teremos que realizar uma manutenção na classe televisão o que deixa evidente um alto grau de acoplamento entre unidades. Um pouco mais a fundo, outro desvio dessa classe é que ela fere um principio SOLID pois não respeita o “principio aberto fechado” (open close principle) pois a medida que um sintonizador novo surge somos obrigados a refazer a classe televisão pois a lógica que trata o sintonizador está chumbada nela.

Entidades de software (classes, módulos, funções, etc) devem ser abertas para a extensão, mas fechadas para modificação – Meyer, Bertrand (1988)

Para resolver esse problema realizaremos uma inversão de controle que prontamente removerá a lógica de instanciação do sintonizador na classe televisão. Para isso podemos usar duas abordagens: obter a instância de sintonizador a partir do uso de um ServiceLocator ou então, realizando uma injeção de dependência. Optaremos neste artigo por uma injeção de dependência do tipo “constructor injection” o que significa que injetaremos a dependência chamando um método construtor.

public class Televisao {

  public final Sintonizador sintonizador;

public class Televisao(final Sintonizador sintonizador){
    this.sintonizador = sintonizador;
  }

public void sintonizar canal(final int canal){
    sintonizador.sintonizar(canal) ;
  }

}

Muito melhor agora! Já não há mais a necessidade de manutenção da classe Televisão pelo simples motivo de se querer atender um novo sintonizador. Afinal, toda vez que surgir um novo sintonizador e houver a necessidade de variá-lo bastará instanciar a televisão com o novo sintonizador, o que ficará a cargo do seu framework de IoC. Outro grande ganho, além da diminuição da carga de manutenção sobre a classe Televisão, se deve ao fato de que uma classe menos complexa possui menor combinação de cenários que deve atender e, justamente por essa característica, tende a ser menos propícia a falhas. Indo um pouco mais além, no que diz respeito aos testes unitários, um objeto que faz o uso de inversão de controle é amplamente mais testável do que um que não o faz! É muito mais fácil simular comportamento da colaboração entre classes a partir do uso de mocks do que sobre objetos concretos.

Por fim, basta agora encontrar um framework que suporta IoC e que melhor se adapte às necessidades do seu sistema como por exemplo o Spring que é amplamente usado no mercado e se dá muito bem com injeção por setters, o Picocontainer que trabalha bem com injeção por construtores assim como o Guice (“juice“) do Google e o VRaptor.

Artigos relacionados

Boas práticas de programação com a aplicação do princípio da responsabilidade única

Referências

OODesign – Open Close Principle

Anúncios