Você deve usar técnicas para resolver os problemas que eles são bons em resolver quando tiver esses problemas. Inversão e injeção de dependência não são diferentes.
A inversão ou injeção de dependência é uma técnica que permite ao seu código decidir sobre qual implementação de um método é chamada em tempo de execução. Isso maximiza os benefícios da ligação tardia. A técnica é necessária quando o idioma não suporta a substituição do tempo de execução de funções que não são de instância. Por exemplo, Java não possui um mecanismo para substituir chamadas para um método estático por chamadas para uma implementação diferente; contraste com o Python, onde tudo o que é necessário para substituir a chamada de função é vincular o nome a uma função diferente (redesigne a variável que contém a função).
Por que queremos variar a implementação da função? Há duas razões principais:
- Queremos usar falsificações para fins de teste. Isso nos permite testar uma classe que depende de uma busca no banco de dados sem realmente conectar-se ao banco de dados.
- Precisamos oferecer suporte a várias implementações. Por exemplo, podemos precisar configurar um sistema que suporte os bancos de dados MySQL e PostgreSQL.
Você também pode anotar a inversão dos contêineres de controle. Esta é uma técnica destinada a ajudá-lo a evitar árvores de construção enormes e emaranhadas que se parecem com este pseudocódigo:
thing5 = new MyThing5();
thing3 = new MyThing3(thing5, new MyThing10());
myApp = new MyApp(
new MyAppDependency1(thing5, thing3),
new MyAppDependency2(
new Thing1(),
new Thing2(new Thing3(thing5, new Thing4(thing5)))
),
...
new MyAppDependency15(thing5)
);
Ele permite que você registre suas aulas e faça a construção para você:
injector.register(Thing1); // Yes, you'd need some kind of actual class reference.
injector.register(Thing2);
...
injector.register(MyAppDepdency15);
injector.register(MyApp);
myApp = injector.create(MyApp); // The injector fills in all the construction parameters.
Observe que é mais simples se as classes registradas puderem ser singletons sem estado .
Palavra de cautela
Observe que a inversão de dependência não deve ser sua resposta ideal para desacoplar a lógica. Procure oportunidades para usar a parametrização . Considere este método de pseudocódigo, por exemplo:
myAverageAboveMin()
{
dbConn = new DbConnection("my connection string");
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", 5);
dbQuery.Execute();
myData = dbQuery.getAll();
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
Poderíamos usar inversão de dependência para algumas partes deste método:
class MyQuerier
{
private _dbConn;
MyQueries(dbConn) { this._dbConn = dbConn; }
fetchAboveMin(min)
{
dbQuery = this._dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
private _querier;
Averager(querier) { this._querier = querier; }
myAverageAboveMin(min)
{
myData = this._querier.fetchAboveMin(min);
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
Mas não devemos, pelo menos não completamente. Observe que criamos uma classe stateful com Querier
. Agora ele contém uma referência a algum objeto de conexão essencialmente global. Isso cria problemas como dificuldade em entender o estado geral do programa e como as diferentes classes se coordenam. Observe também que somos forçados a falsificar o consultante ou a conexão, se quisermos testar a lógica da média. Além disso, uma abordagem melhor seria aumentar a parametrização :
class MyQuerier
{
fetchAboveMin(dbConn, min)
{
dbQuery = dbConn.makeQuery();
dbQuery.Command = "SELECT * FROM MY_DATA WHERE x > :min";
dbQuery.setParam("min", min);
dbQuery.Execute();
return dbQuery.getAll();
}
}
class Averager
{
averageData(myData)
{
count = 0;
total = 0;
foreach (row in myData)
{
count++;
total += row.x;
}
return total / count;
}
class StuffDoer
{
private _querier;
private _averager;
StuffDoer(querier, averager)
{
this._querier = querier;
this._averager = averager;
}
myAverageAboveMin(dbConn, min)
{
myData = this._querier.fetchAboveMin(dbConn, min);
return this._averager.averageData(myData);
}
}
E a conexão seria gerenciada em um nível ainda mais alto que é responsável pela operação como um todo e sabe o que fazer com essa saída.
Agora, podemos testar a lógica da média de maneira completamente independente da consulta e, além disso, podemos usá-la em uma variedade maior de situações. Podemos questionar se precisa mesmo os MyQuerier
e Averager
objetos, e talvez a resposta é que não fazer se não temos a intenção de testar a unidade StuffDoer
, e não o teste de unidade StuffDoer
seria perfeitamente razoável, já que ele é tão intimamente ligado ao banco de dados. Pode fazer mais sentido deixar apenas os testes de integração cobri-lo. Nesse caso, poderíamos estar fazendo bem fetchAboveMin
e averageData
em métodos estáticos.