Myślę, że najłatwiejszym sposobem na zrozumienie różnicy między tymi dwoma i dlaczego kontener DI jest o wiele lepszy niż lokalizator usług, to zastanowienie się, dlaczego w pierwszej kolejności dokonujemy inwersji zależności.
Dokonujemy inwersji zależności, aby każda klasa wyraźnie określała dokładnie, od czego zależy działanie. Robimy to, ponieważ tworzy to najsłabsze sprzęgło, jakie możemy osiągnąć. Im luźniejsze sprzężenie, tym łatwiej coś przetestować i zrefaktoryzować (i generalnie wymaga to najmniej refaktoryzacji w przyszłości, ponieważ kod jest czystszy).
Spójrzmy na następującą klasę:
public class MySpecialStringWriter
{
private readonly IOutputProvider outputProvider;
public MySpecialFormatter(IOutputProvider outputProvider)
{
this.outputProvider = outputProvider;
}
public void OutputString(string source)
{
this.outputProvider.Output("This is the string that was passed: " + source);
}
}
W tej klasie wyraźnie stwierdzamy, że potrzebujemy IOutputProvider i nic więcej, aby ta klasa działała. Jest to w pełni testowalne i zależy od jednego interfejsu. Mogę przenieść tę klasę do dowolnego miejsca w mojej aplikacji, w tym do innego projektu, a wszystko czego potrzebuje to dostęp do interfejsu IOutputProvider. Jeśli inni programiści chcą dodać coś nowego do tej klasy, co wymaga drugiej zależności, muszą wyraźnie powiedzieć, czego potrzebują w konstruktorze.
Spójrz na tę samą klasę z lokalizatorem usług:
public class MySpecialStringWriter
{
private readonly ServiceLocator serviceLocator;
public MySpecialFormatter(ServiceLocator serviceLocator)
{
this.serviceLocator = serviceLocator;
}
public void OutputString(string source)
{
this.serviceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
Teraz dodałem lokalizator usług jako zależność. Oto problemy, które są od razu oczywiste:
- Pierwszym problemem jest to, że potrzeba więcej kodu, aby osiągnąć ten sam wynik. Więcej kodu jest złe. To niewiele więcej kodu, ale wciąż więcej.
- Drugi problem polega na tym, że moja zależność nie jest już wyraźna . Nadal muszę wstrzyknąć coś do klasy. Z wyjątkiem teraz rzecz, której chcę, nie jest wyraźna. Jest ukryty we właściwości rzeczy, o którą prosiłem. Teraz potrzebuję dostępu zarówno do ServiceLocator, jak i IOutputProvider, jeśli chcę przenieść klasę do innego zestawu.
- Trzeci problem polega na tym, że dodatkowa zależność może zostać przejęta przez innego programistę, który nawet nie zdaje sobie sprawy, że bierze ją, gdy dodaje kod do klasy.
- W końcu ten kod jest trudniejszy do przetestowania (nawet jeśli ServiceLocator jest interfejsem), ponieważ musimy wyśmiewać ServiceLocator i IOutputProvider zamiast tylko IOutputProvider
Dlaczego więc nie uczynić lokalizatora usług klasą statyczną? Spójrzmy:
public class MySpecialStringWriter
{
public void OutputString(string source)
{
ServiceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
To jest o wiele prostsze, prawda?
Źle.
Powiedzmy, że IOutputProvider jest implementowany przez bardzo długo działającą usługę internetową, która zapisuje ciąg znaków w piętnastu różnych bazach danych na całym świecie i zajmuje bardzo dużo czasu.
Spróbujmy przetestować tę klasę. Do testu potrzebujemy innej implementacji IOutputProvider. Jak piszemy test?
Aby to zrobić, musimy wykonać fantazyjną konfigurację w statycznej klasie ServiceLocator, aby użyć innej implementacji IOutputProvider, gdy jest ona wywoływana przez test. Nawet napisanie tego zdania było bolesne. Wdrożenie tego byłoby torturujące i byłoby koszmarem konserwacyjnym . Nigdy nie powinniśmy musieć modyfikować klasy specjalnie do testowania, szczególnie jeśli ta klasa nie jest klasą, którą faktycznie próbujemy przetestować.
Pozostaje Ci albo a) test, który powoduje natrętne zmiany kodu w niepowiązanej klasie ServiceLocator; lub b) brak testu. I pozostaje Ci również mniej elastyczne rozwiązanie.
Zatem klasa lokalizatora usług musi zostać wstrzyknięta do konstruktora. Co oznacza, że pozostały nam konkretne problemy wspomniane wcześniej. Lokalizator usług wymaga więcej kodu, mówi innym programistom, że potrzebuje rzeczy, których nie potrzebuje, zachęca innych programistów do pisania gorszego kodu i daje nam mniejszą elastyczność w posuwaniu się naprzód.
Mówiąc prosto , lokalizatory usług zwiększają sprzężenie w aplikacji i zachęcają innych programistów do pisania wysoce sprzężonego kodu .