Você deve usar técnicas para resolver os problemas que eles são bons em resolver quando você tem esses problemas. Inversão e injeção de dependência não são diferentes.
Inversão ou injeção de dependência é uma técnica que permite que seu código decida em 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 a linguagem 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 com 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 (reatribuir 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 de banco de dados sem realmente se conectar ao banco de dados.
- Precisamos dar 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 querer observar a inversão de contêineres de controle. Essa é uma técnica destinada a ajudar você a evitar árvores de construção enormes e emaranhadas que se parecem com esse 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, em seguida, 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.
Note que é mais simples se as classes registradas puderem ser singletons sem estado .
Palavra de cautela
Note que a inversão de dependência não deve ser sua resposta 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 a inversão de dependência para algumas partes desse 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 entre si. Note também que somos forçados a falsificar o querier ou a conexão se quisermos testar a lógica da média. Além disso, uma abordagem melhor seria aumentar 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 de média completamente independentemente da consulta e, além disso, podemos usá-la em uma variedade maior de situações. Podemos questionar se precisamos mesmo dos objetos MyQuerier
e Averager
, e talvez a resposta seja que nós não pretendemos testar a unidade StuffDoer
, e não o teste unitário StuffDoer
seria perfeitamente razoável, já que é tão strongmente acoplado ao banco de dados. Pode fazer mais sentido apenas deixar os testes de integração cobri-lo. Nesse caso, podemos estar fazendo fetchAboveMin
e averageData
em métodos estáticos.