Prenons un exemple simple - vous injectez peut-être un moyen de journalisation.
Injecter une classe
class Worker: IWorker
{
ILogger _logger;
Worker(ILogger logger)
{
_logger = logger;
}
void SomeMethod()
{
_logger.Debug("This is a debug log statement.");
}
}
Je pense que c'est assez clair ce qui se passe. De plus, si vous utilisez un conteneur IoC, vous n'avez même pas besoin d'injecter quoi que ce soit explicitement, vous ajoutez simplement à votre racine de composition:
container.RegisterType<ILogger, ConcreteLogger>();
container.RegisterType<IWorker, Worker>();
....
var worker = container.Resolve<IWorker>();
Lors du débogage Worker
, un développeur n'a qu'à consulter la racine de la composition pour déterminer quelle classe concrète est utilisée.
Si un développeur a besoin d'une logique plus compliquée, il a toute l'interface avec laquelle travailler:
void SomeMethod()
{
if (_logger.IsDebugEnabled) {
_logger.Debug("This is a debug log statement.");
}
}
Injection d'une méthode
class Worker
{
Action<string> _methodThatLogs;
Worker(Action<string> methodThatLogs)
{
_methodThatLogs = methodThatLogs;
}
void SomeMethod()
{
_methodThatLogs("This is a logging statement");
}
}
Tout d' abord, notez que le paramètre constructeur a un nom plus maintenant, methodThatLogs
. Ceci est nécessaire car vous ne pouvez pas dire ce que vous êtes Action<string>
censé faire. Avec l'interface, c'était complètement clair, mais ici, nous devons recourir à la dénomination des paramètres. Cela semble intrinsèquement moins fiable et plus difficile à appliquer lors d'une génération.
Maintenant, comment injectons-nous cette méthode? Eh bien, le conteneur IoC ne le fera pas pour vous. Il vous reste donc à l'injecter explicitement lorsque vous instanciez Worker
. Cela soulève quelques problèmes:
- C'est plus de travail pour instancier un
Worker
- Les développeurs qui tentent de déboguer
Worker
trouveront qu'il est plus difficile de déterminer quelle instance concrète est appelée. Ils ne peuvent pas simplement consulter la racine de la composition; ils devront tracer à travers le code.
Et si nous avions besoin d'une logique plus compliquée? Votre technique n'expose qu'une seule méthode. Maintenant, je suppose que vous pouvez faire cuire les choses compliquées dans le lambda:
var worker = new Worker((s) => { if (log.IsDebugEnabled) log.Debug(s) } );
mais quand vous écrivez vos tests unitaires, comment testez-vous cette expression lambda? Il est anonyme, donc votre framework de test unitaire ne peut pas l'instancier directement. Peut-être que vous pouvez trouver un moyen intelligent de le faire, mais ce sera probablement un PITA plus grand que d'utiliser une interface.
Résumé des différences:
- Injecter uniquement une méthode rend plus difficile à déduire le but, tandis qu'une interface communique clairement le but.
- L'injection d'une méthode uniquement expose moins de fonctionnalités à la classe qui reçoit l'injection. Même si vous n'en avez pas besoin aujourd'hui, vous en aurez peut-être besoin demain.
- Vous ne pouvez pas injecter automatiquement uniquement une méthode à l'aide d'un conteneur IoC.
- Vous ne pouvez pas dire à partir de la racine de composition quelle classe concrète est à l'œuvre dans une instance particulière.
- Il est difficile de tester uniquement l'expression lambda elle-même.
Si vous êtes d'accord avec tout ce qui précède, vous pouvez injecter uniquement la méthode. Sinon, je vous suggère de vous en tenir à la tradition et d'injecter une interface.