Weźmy prosty przykład - być może wstrzykujesz sposób logowania.
Wstrzykiwanie klasy
class Worker: IWorker
{
ILogger _logger;
Worker(ILogger logger)
{
_logger = logger;
}
void SomeMethod()
{
_logger.Debug("This is a debug log statement.");
}
}
Myślę, że to całkiem jasne, co się dzieje. Co więcej, jeśli używasz kontenera IoC, nie musisz nawet wstrzykiwać niczego jawnie, po prostu dodajesz do katalogu głównego kompozycji:
container.RegisterType<ILogger, ConcreteLogger>();
container.RegisterType<IWorker, Worker>();
....
var worker = container.Resolve<IWorker>();
Podczas debugowania Worker
programista musi tylko skonsultować się z katalogiem głównym kompozycji, aby ustalić, która konkretna klasa jest używana.
Jeśli programista potrzebuje bardziej skomplikowanej logiki, ma cały interfejs do pracy z:
void SomeMethod()
{
if (_logger.IsDebugEnabled) {
_logger.Debug("This is a debug log statement.");
}
}
Wstrzyknięcie metody
class Worker
{
Action<string> _methodThatLogs;
Worker(Action<string> methodThatLogs)
{
_methodThatLogs = methodThatLogs;
}
void SomeMethod()
{
_methodThatLogs("This is a logging statement");
}
}
Po pierwsze należy zauważyć, że parametr konstruktora ma dłuższą nazwę teraz methodThatLogs
. Jest to konieczne, ponieważ nie możesz powiedzieć, co Action<string>
ma zrobić. Z interfejsem było to całkowicie jasne, ale tutaj musimy uciekać się do polegania na nazywaniu parametrów. Wydaje się to z natury mniej niezawodne i trudniejsze do wyegzekwowania podczas kompilacji.
Jak wstrzyknąć tę metodę? Kontener IoC nie zrobi tego za ciebie. Pozostaje ci to jawnie wstrzykiwać, gdy tworzysz instancję Worker
. Rodzi to kilka problemów:
- Tworzenie instancji zajmuje więcej czasu
Worker
- Programiści próbujący debugować
Worker
stwierdzą, że trudniej jest ustalić, jak wywoływana jest konkretna instancja. Nie mogą po prostu skonsultować się z katalogiem głównym kompozycji; będą musieli prześledzić kod.
A może potrzebujemy bardziej skomplikowanej logiki? Twoja technika ujawnia tylko jedną metodę. Teraz przypuszczam, że możesz upiec skomplikowane rzeczy w lambda:
var worker = new Worker((s) => { if (log.IsDebugEnabled) log.Debug(s) } );
ale kiedy piszesz testy jednostkowe, jak testujesz to wyrażenie lambda? Jest anonimowy, więc środowisko testów jednostkowych nie może go bezpośrednio utworzyć. Może uda ci się wymyślić jakiś sprytny sposób, ale prawdopodobnie będzie to większa PITA niż przy użyciu interfejsu.
Podsumowanie różnic:
- Wstrzyknięcie tylko metody utrudnia wnioskowanie o celu, podczas gdy interfejs wyraźnie komunikuje cel.
- Wstrzyknięcie tylko metody naraża mniej funkcjonalność klasy otrzymującej zastrzyk. Nawet jeśli nie potrzebujesz go dzisiaj, możesz potrzebować go jutro.
- Nie można automatycznie wstrzykiwać tylko metody przy użyciu kontenera IoC.
- Z katalogu głównego nie można stwierdzić, która konkretna klasa działa w konkretnym przypadku.
- Problemem jest testowanie jednostkowe samej ekspresji lambda.
Jeśli wszystko jest w porządku, możesz podać tylko tę metodę. W przeciwnym razie sugeruję trzymać się tradycji i wstrzykiwać interfejs.