Escolhendo entre injeção de dependências por construtores e injeção de dependências por setters

Durante o post anterior tivemos a oportunidade de entender o porque de a inversão de controle (IoC) vir a ser considerada uma boa prática e pudemos esclarecer a diferença conceitual entre inversão de controle e injeção de dependência. Pudemos também entender que, em geral, para se alcançar a IoC fazemos o uso de algum framework que facilite uma injeção de dependências tal quais o Spring, o Guice e o PicoContainer. Neste artigo tenho a intenção de discutir características, vantagens e desvantagens das duas formas mais conhecidas de injeção de dependências: a injeção por construtor e a injeção por setters.

Dentre as duas técnicas mais amplamente conhecidas para a injeção de dependências temos que a mais amplamente utilizada, segundo os dados da SpringSource, é a que faz o uso de setters e a justificativa é histórica. Basicamente, acontece que o Spring (hoje o framework de IoC mais utilizado no mercado) nasceu focando principalmente essa característica pois na época em que as primeiras versões desse framework foram lançadas, em meados de junho de 2003, havia o consenso de que o uso de setters estimularia uma ferramenta mais facilmente configurável e resultaria em melhor entendimento da ferramenta pois proveria configurações com valores padrões para os colaboradores das classes do sistema. O sucesso então alcançado por eles foi o que influenciou tantos outros a copiar e disseminar essa idéia para vários outros sistemas.

Já em um pensamento oposto ao do Spring, surgiram de forma pioneira outros frameworks que lidavam fortemente com injeção de dependências através do uso de métodos construtores como é o caso hoje do Picocontainer e o Guice (leia-se Juice). Para estas, focar em injeção por construtores privilegia o desenvolvimento de código de aplicação e não no próprio framework.

Qual alternativa é a melhor? Depende. Cada situação é particular. Vejamos um exemplo de injeção de dependências através do uso de setters:

public class MinhaClasse {

  private MeuRecurso meuRecurso;

  public MinhaClasse() { }

  public MeuRecurso getMeuRecurso(){
    return this.meuRecurso;
  }

  public setMeuRecurso(MeuRecurso meuRecurso){
    this.meuRecurso = meuRecurso;
  }

  public seuMetodoQueUsaORecurso(){
    meuRecurso.fazAlgumaCoisa();
  }
}

A partir do exemplo anterior percebemos que a MinhaClasse faz o uso de um método construtor vazio, sem argumentos, porém define o meuRecurso a partir de um método set tornando possível instanciar a classe sem a necessidade de definir qualquer propriedade padrão. Essa técnica ainda viabiliza mudarmos o meuRecurso a medida em que precisarmos mas, por outro lado, também permite que essa propriedade nunca seja definida. Essa propriedade vem da ideia de que uma classe deva ser configurável e possa ser instanciada a partir de valores defaults previamente definidos.

De forma oposta, com o uso de injeção por construtores, temos em geral algo como o seguinte exemplo:

public class MinhaClasse {

  private final MeuRecurso meuRecurso;

  public MinhaClasse(final MeuRecurso meuRecurso) {
    this.meuRecurso = meuRecurso;
  }

  public seuMetodoQueUsaORecurso(){
    meuRecurso.fazAlgumaCoisa();
  }
}

A injeção de dependências por método construtor tem como característica considerar que os colaboradores de uma classe são obrigatórios e estáticos (não variam). Em outras palavras, entendem que uma classe não deve ser instanciada se não possuir seus colaboradores previamente definidos. É também característica dessa técnica tornar a classe mais objetiva, com menos linhas de código de verificação, fazendo o uso do modificador final. Assim, a classe MinhaClasse só é instanciada quando contiver um meuRecurso definido.

Portanto, entre as duas alternativas, geralmente a injeção por construtores parece mais elegante para código de aplicação. Afinal, além de tornar o código fonte mais objetivo evitando o uso abusivo de getters e setters pouco usados, só permite a instanciação de classes que tenham todos os colaboradores obrigatórios satisfeitos evitando assim muitos dos erros de NullPointer causados por colaboradores não previamente setados. Essa técnica também garante que os colaboradores não serão variados durante o ciclo de vida da aplicação, o que é uma característica recorrente por exemplo de um DAO já que são frequentemente injetados em classes, mas raramente são variados durante o ciclo de vida da aplicação. Outra vantagem do uso de injeção de dependências por construtores é que ao combinar variáveis com o modificador final, imediatamente nos beneficiamos de maior segurança face a um ambiente multithread.

Ainda no que diz respeito as diferenças entre as duas abordagens, devemos levar em consideração que ao usarmos injeção por construtores acabamos evitando a ocorrência de dependências circulares, pois é garantida uma hierarquia bem definida, em contraposição àquela que faz o uso de setters que não deixam isso bem claro. Além disso, quando o último objeto for instanciado e estamos usando a injeção por construtores, significa que a aplicação está completa. Por outro lado, é verdade que com essa técnica uma classe pode vir a ter inúmeros colaboradores e, portanto, muitos argumentos, mas também é verdade que, ao chegar nesse ponto, temos fortes indícios que ela tem muitas responsabilidades e portanto precisa ser refatorada seguindo o principio da responsabilidade única.

Então é sempre melhor usar injeção de dependências através de construtores? Existem casos em que isso não é verdade e está aí o Spring para comprovar. Frameworks, classes altamente configuráveis e classes que fazem muito uso de herança tendem a recorrer à injeção por setters de forma a torná-las mais configuráveis. Sendo assim, podemos partir do pressuposto de que devemos sempre recorrer à injeção de dependências através de métodos construtores quando temos colaboradores que são indispensáveis para a criação de uma determinada classe e usar getters e setters para aquelas propriedades variáveis configuráveis. Em outras palavras, devemos fazer uso do método construtor para garantir que todas as dependências obrigatórias serão satisfeitas sem que nos tornemos reféns de um mecanismo dedicado (o famoso if objeto != null) para garantir que as propriedades foram realmente definidas.

Posts relacionados

Boas práticas de programação com a aplicação do princípio da responsabilidade única
Desmistificando o conceito de inversão de controle e sua relação com a injeção de dependência

Artigos relacionados

Arendsen, Alef – Setter injection versus constructor injection and the use of @Required
Cuesta, Alex – Constructor Injection vs Setter Injection
Hevery, Misko – Constructor Injection vs. Setter Injection

Anúncios