Przepraszam, jeśli wydaje się to kolejną powtórką pytania, ale za każdym razem, gdy znajduję artykuł na ten temat, głównie mówi on o tym, czym jest DI. Tak, dostaję DI, ale staram się zrozumieć potrzebę kontenera IoC, do którego wszyscy chyba się pakują. Czy celem kontenera IoC jest po prostu „automatyczne rozwiązywanie” konkretnej implementacji zależności? Może moje klasy zwykle nie mają kilku zależności i może dlatego nie widzę wielkiego problemu, ale chcę się upewnić, że dobrze rozumiem użyteczność kontenera.
Zazwyczaj dzielę logikę biznesową na klasy, które mogą wyglądać mniej więcej tak:
public class SomeBusinessOperation
{
private readonly IDataRepository _repository;
public SomeBusinessOperation(IDataRespository repository = null)
{
_repository = repository ?? new ConcreteRepository();
}
public SomeType Run(SomeRequestType request)
{
// do work...
var results = _repository.GetThings(request);
return results;
}
}
Więc ma tylko jedną zależność, aw niektórych przypadkach może mieć drugą lub trzecią, ale nie tak często. Tak więc wszystko, co to wywołuje, może przejść własne repozytorium lub pozwolić na użycie domyślnego repozytorium.
O ile obecnie rozumiem kontener IoC, wszystko, co robi kontener, to IDataRepository. Ale jeśli to wszystko, co robi, to nie widzę w tym mnóstwa wartości, ponieważ moje klasy operacyjne już definiują awarię, gdy nie przechodzi żadna zależność. Więc jedyną inną korzyścią, o której mogę myśleć, jest to, że jeśli mam kilka operacji, takich jak używając tego samego repozytorium rezerwowego, mogę zmienić to repozytorium w jednym miejscu, którym jest rejestr / fabryka / kontener. I to świetnie, ale czy o to chodzi?
źródło
ConcreteRepository
i (2) można podać dodatkowe zależnościConcreteRepository
(na przykład połączenie z bazą danych byłoby powszechne).Odpowiedzi:
Kontener IoC nie dotyczy przypadku, w którym masz jedną zależność. Chodzi o przypadek, w którym masz 3 zależności i mają one kilka zależności, które mają zależności itp.
Pomaga także scentralizować rozwiązywanie zależności i zarządzanie nimi w cyklu życia.
źródło
Istnieje wiele powodów, dla których warto użyć kontenera IoC.
Niepowiązane biblioteki dll
Możesz użyć kontenera IoC, aby rozwiązać konkretną klasę z dll bez odwołania. Oznacza to, że możesz wziąć zależności całkowicie od abstrakcji - tj. Interfejsu.
Unikaj używania
new
Kontener IoC oznacza, że możesz całkowicie usunąć użycie
new
słowa kluczowego do tworzenia klasy. Ma to dwa efekty. Pierwszym jest to, że oddziela twoje zajęcia. Druga (związana z tym kwestia) polega na tym, że można poddawać się próbnym testom jednostkowym. Jest to niezwykle przydatne, szczególnie w przypadku długotrwałego procesu.Napisz przeciw abstrakcjom
Użycie kontenera IoC do rozwiązania konkretnych zależności pozwala na pisanie kodu przeciw abstrakcjom, zamiast implementowania każdej konkretnej klasy, jakiej potrzebujesz. Na przykład może być potrzebny kod do odczytu danych z bazy danych. Zamiast pisać klasę interakcji z bazą danych, po prostu piszesz dla niej interfejs i kodujesz przeciw temu. Możesz użyć próbnego narzędzia do przetestowania funkcjonalności tworzonego kodu podczas jego opracowywania, zamiast polegać na opracowaniu konkretnej klasy interakcji bazy danych przed przetestowaniem drugiego kodu.
Unikaj łamliwego kodu
Innym powodem korzystania z kontenera IoC jest to, że polegając na kontenerze IoC w celu rozwiązania zależności, można uniknąć konieczności zmiany każdego wywołania do konstruktora klasy podczas dodawania lub usuwania zależności. Kontener IoC automatycznie rozwiąże twoje zależności. Nie jest to ogromny problem, gdy tworzysz klasę raz, ale jest to gigantyczny problem, gdy tworzysz klasę w stu miejscach.
Dożywotnie zarządzanie i niezarządzane czyszczenie zasobów
Ostatnim powodem, o którym wspomnę, jest zarządzanie czasem życia obiektów. Kontenery IoC często umożliwiają określenie czasu życia obiektu. Sensowne jest określenie czasu życia obiektu w kontenerze IoC zamiast próby ręcznego zarządzania nim w kodzie. Ręczne zarządzanie cyklem życia może być bardzo trudne. Może to być przydatne w przypadku obiektów wymagających utylizacji. Zamiast ręcznie zarządzać usuwaniem obiektów, niektóre kontenery IoC zarządzają usuwaniem dla Ciebie, co może pomóc w zapobieganiu wyciekom pamięci i uprościć bazę kodu.
Problem z podanym przez ciebie przykładowym kodem polega na tym, że pisana klasa ma konkretną zależność od klasy ConcreteRepository. Kontener IoC usunąłby tę zależność.
źródło
Zgodnie z zasadą jednej odpowiedzialności każda klasa musi ponosić tylko jedną odpowiedzialność. Tworzenie nowych instancji klas to po prostu kolejna odpowiedzialność, więc musisz obudować ten rodzaj kodu w jednej lub kilku klasach. Możesz to zrobić przy użyciu dowolnych wzorców kreacyjnych, na przykład fabryk, konstruktorów, kontenerów DI itp.
Istnieją inne zasady, takie jak odwrócenie kontroli i odwrócenie zależności. W tym kontekście są one związane z wystąpieniem zależności. Stwierdzają, że klasy wysokiego poziomu muszą być oddzielone od klas niskiego poziomu (zależności), których używają. Możemy oddzielić rzeczy, tworząc interfejsy. Tak więc klasy niskiego poziomu muszą implementować określone interfejsy, a klasy wysokiego poziomu muszą wykorzystywać instancje klas, które implementują te interfejsy. (uwaga: jednolite ograniczenie interfejsu REST stosuje to samo podejście na poziomie systemu).
Kombinacja tych zasad na przykładzie (przepraszam za kod niskiej jakości, użyłem jakiegoś języka ad-hoc zamiast C #, ponieważ nie wiem tego):
Bez SRP, bez IoC
Bliżej SRP, bez IoC
Tak SRP, nie IoC
Tak SRP, tak IoC
W rezultacie otrzymaliśmy kod, w którym możesz zastąpić każdą konkretną implementację inną, która implementuje ten sam interfejs ofc. Jest to dobre, ponieważ uczestniczące klasy są od siebie oddzielone, znają tylko interfejsy. Kolejną zaletą jest to, że kod instancji jest wielokrotnego użytku.
źródło