Vous devez utiliser des techniques pour résoudre les problèmes qu’ils maîtrisent bien lorsque vous avez ces problèmes. L'inversion de dépendance et l'injection ne sont pas différentes.
L'inversion de dépendance ou l'injection est une technique qui permet à votre code de décider de quelle implémentation d'une méthode est appelée au moment de l'exécution. Cela maximise les avantages d'une liaison tardive. Cette technique est nécessaire lorsque le langage ne prend pas en charge le remplacement à l'exécution de fonctions autres que l'instance. Par exemple, Java ne dispose pas d'un mécanisme permettant de remplacer les appels d'une méthode statique par des appels d'une autre implémentation; contraste avec Python, où tout ce qui est nécessaire pour remplacer la fonction est de lier le nom à une autre fonction (réaffecter la variable contenant la fonction).
Pourquoi voudrions-nous faire varier la mise en œuvre de la fonction? Il y a deux raisons principales:
- Nous voulons utiliser des faux pour des tests. Cela nous permet de tester une classe qui dépend d'une extraction de base de données sans se connecter réellement à la base de données.
- Nous devons prendre en charge plusieurs implémentations. Par exemple, il peut être nécessaire de configurer un système prenant en charge les bases de données MySQL et PostgreSQL.
Vous pouvez également prendre note de l'inversion des conteneurs de contrôle. C'est une technique destinée à vous aider à éviter les arbres de construction énormes et enchevêtrés qui ressemblent à ce pseudocode:
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)
);
Il vous permet d’inscrire vos cours et d’effectuer ensuite la construction pour vous:
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.
Notez que c'est plus simple si les classes enregistrées peuvent être des singletons sans état .
Mot d'avertissement
Notez que l'inversion de dépendance ne devrait pas être votre solution idéale pour la logique de découplage. Recherchez les possibilités d'utiliser le paramétrage à la place. Considérons cette méthode de pseudocode par exemple:
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;
}
Nous pourrions utiliser l'inversion de dépendance pour certaines parties de cette méthode:
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;
}
Mais nous ne devrions pas, du moins pas complètement. Notez que nous avons créé une stateful classe Querier
. Il contient maintenant une référence à un objet de connexion essentiellement global. Cela crée des problèmes tels que la difficulté à comprendre l'état général du programme et la coordination des différentes classes. Notez également que nous sommes obligés de simuler le demandeur ou la connexion si nous voulons tester la logique de calcul de la moyenne. En outre, une meilleure approche consisterait à augmenter le paramétrage :
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);
}
}
Et la connexion serait gérée à un niveau encore plus élevé, responsable de l'opération dans son ensemble et sachant quoi faire avec cette sortie.
Nous pouvons maintenant tester la logique de calcul de la moyenne de manière totalement indépendante de l'interrogation, et nous pouvons en outre l'utiliser dans une plus grande variété de situations. Nous pouvons nous demander si nous avons même besoin des objets MyQuerier
et Averager
, et la réponse est peut-être que nous n'en avons pas si nous n'avons pas l'intention de procéder à des tests unitaires StuffDoer
, et que les tests unitaires StuffDoer
seraient parfaitement raisonnables, car ils sont si étroitement liés à la base de données. Il serait peut-être plus judicieux de laisser les tests d'intégration le couvrir. Dans ce cas, nous pourrions être bien prise fetchAboveMin
et averageData
dans les méthodes statiques.