A injeção de dependência (DI) não é um conceito fácil de entender e aplicá-lo a aplicativos novos ou existentes é ainda mais confuso. Jess Smith mostra como fazer injeção de dependência sem um contêiner de injeção nas linguagens de programação C# e Java. Neste artigo, mostrarei como implementar injeção de dependência (DI) em aplicativos .NET e Java. O conceito de injeção de dependência chamou a atenção dos desenvolvedores pela primeira vez em 2000, quando Robert Martin escreveu o artigo “Design Principles and Patterns” (mais tarde conhecido pela sigla
SOLID ). O D em SOLID refere-se à Dependência de Inversão (DOI), que mais tarde ficou conhecida como injeção de dependência. A definição original e mais comum: inversão de dependência é uma inversão da forma como uma classe base gerencia dependências. O artigo original de Martin usou o seguinte código para ilustrar a dependência de uma classe
Copy
em uma classe de nível inferior
WritePrinter
:
void Copy()
{
int c;
while ((c = ReadKeyboard()) != EOF)
WritePrinter(c);
}
O primeiro problema óbvio é que se você alterar a lista de parâmetros ou os tipos de um método
WritePrinter
, precisará implementar atualizações sempre que houver dependência desse método. Este processo aumenta os custos de manutenção e é uma fonte potencial de novos erros.
Outro problema: a classe Copy não é mais uma candidata potencial para reutilização. Por exemplo, e se você precisar enviar os caracteres inseridos no teclado para um arquivo em vez de para uma impressora? Para fazer isso, você pode modificar a classe
Copy
da seguinte maneira (sintaxe da linguagem C++):
void Copy(outputDevice dev)
{
int c;
while ((c = ReadKeyboard()) != EOF)
if (dev == printer)
WritePrinter(c);
else
WriteDisk(c);
}
Apesar da introdução de uma nova dependência
WriteDisk
, a situação não melhorou (mas piorou) porque outro princípio foi violado: "entidades de software, isto é, classes, módulos, funções, e assim por diante, devem estar abertas para extensão, mas fechadas para modificação." Martin explica que essas novas instruções condicionais if/else reduzem a estabilidade e a flexibilidade do código. A solução é inverter as dependências para que os métodos de gravação e leitura dependam do
Copy
. Em vez de "empurrar" dependências, elas são passadas pelo construtor. O código modificado fica assim:
class Reader
{
public:
virtual int Read() = 0;
};
class Writer
{
public:
virtual void Write(char) = 0;
};
void Copy(Reader& r, Writer& w)
{
int c;
while((c=r.Read()) != EOF)
w.Write(c);
}
Agora a classe
Copy
pode ser facilmente reutilizada com diferentes implementações de métodos de classe
Reader
e
Writer
. A classe
Copy
não possui nenhuma informação sobre a estrutura interna dos tipos
Reader
e
Writer
, possibilitando reutilizá-los com diferentes implementações. Mas se tudo isso parece uma espécie de bobagem para você, talvez os exemplos a seguir em Java e C# esclareçam a situação.
Exemplo em Java e C#
Para ilustrar a facilidade da injeção de dependência sem um contêiner de dependência, vamos começar com um exemplo simples que pode ser personalizado para uso
DI
em apenas algumas etapas. Digamos que temos uma classe
HtmlUserPresentation
que, quando seus métodos são chamados, gera uma interface de usuário HTML. Aqui está um exemplo simples:
HtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
Qualquer projeto que utilize esse código de classe terá uma dependência da classe
HtmlUserPresentation
, resultando nos problemas de usabilidade e manutenção descritos acima. Uma melhoria surge imediatamente: criar uma interface com assinaturas de todos os métodos atualmente disponíveis na classe
HtmlUserPresentation
. Aqui está um exemplo desta interface:
public interface IHtmlUserPresentation {
String createTable(ArrayList rowVals, String caption);
String createTableRow(String tableCol);
}
Após criar a interface, modificamos a classe
HtmlUserPresentation
para utilizá-la. Voltando a instanciar o type
HtmlUserPresentation
, agora podemos usar o tipo interface em vez do tipo base:
IHtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation();
String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
A criação de uma interface nos permite usar facilmente outras implementações do
IHtmlUserPresentation
. Por exemplo, se quisermos testar este tipo, podemos facilmente substituir o tipo base
HtmlUserPresentation
por outro tipo chamado
HtmlUserPresentationTest
. As alterações feitas até agora tornam o código mais fácil de testar, manter e escalar, mas não fazem nada para reutilização, pois todas as
HtmlUserPresentation
classes que usam o tipo ainda estão cientes de sua existência. Para remover essa dependência direta, você pode passar um tipo de interface
IHtmlUserPresentation
para o construtor (ou lista de parâmetros do método) da classe ou método que irá utilizá-la:
public UploadFile(IHtmlUserPresentation htmlUserPresentation)
O construtor
UploadFile
agora tem acesso a todas as funcionalidades do tipo
IHtmlUserPresentation
, mas nada sabe sobre a estrutura interna da classe que implementa esta interface. Neste contexto, a injeção de tipo ocorre quando uma instância da classe é criada
UploadFile
. Um tipo de interface
IHtmlUserPresentation
torna-se reutilizável ao passar diferentes implementações para diferentes classes ou métodos que requerem funcionalidades diferentes.
Conclusão e recomendações para consolidação do material
Você aprendeu sobre injeção de dependência e que diz-se que as classes dependem diretamente umas das outras quando uma delas instancia outra para obter acesso à funcionalidade do tipo de destino. Para dissociar a dependência direta entre os dois tipos, você deve criar uma interface. Uma interface dá a um tipo a capacidade de incluir diferentes implementações, dependendo do contexto da funcionalidade necessária. Ao passar um tipo de interface para um construtor ou método de classe, a classe/método que precisa da funcionalidade não conhece nenhum detalhe sobre o tipo que implementa a interface. Por causa disso, um tipo de interface pode ser reutilizado em diferentes classes que exigem comportamento semelhante, mas não idêntico.
- Para experimentar a injeção de dependência, observe seu código de um ou mais aplicativos e tente converter um tipo base muito usado em uma interface.
- Altere as classes que instanciam diretamente esse tipo base para utilizar esse novo tipo de interface e passe-o pelo construtor ou lista de parâmetros do método da classe que irá utilizá-lo.
- Crie uma implementação de teste para testar esse tipo de interface. Depois que seu código for refatorado,
DI
ele se tornará mais fácil de implementar e você notará como seu aplicativo se tornará muito mais flexível em termos de reutilização e manutenção.
GO TO FULL VERSION