Przejdę przez twoje punkty numerycznie, ale najpierw jest coś, na co powinieneś bardzo uważać: nie powielaj sposobu, w jaki konsument korzysta z biblioteki z jej implementacją . Dobrymi przykładami tego są Entity Framework (który sam cytujesz jako dobrą bibliotekę) i MVC ASP.NET. Oba robią okropnie dużo pod maską, na przykład z odbiciem, co absolutnie nie byłoby uważane za dobry projekt, jeśli rozpowszechnisz go za pomocą codziennego kodu.
Te biblioteki absolutnie nie są „transparentne” w tym, jak działają i co robią za kulisami. Ale to nie jest szkodliwe, ponieważ wspierają dobre zasady programowania u swoich konsumentów . Kiedy więc mówisz o takiej bibliotece, pamiętaj, że jako konsument biblioteki, nie musisz martwić się o jej implementację lub utrzymanie. Powinieneś martwić się tylko, w jaki sposób pomaga lub przeszkadza w pisaniu kodu korzystającego z biblioteki. Nie łącz tych koncepcji!
Aby przejść przez punkt:
Natychmiast dochodzimy do tego, co mogę jedynie założyć, jest przykładem powyższego. Mówisz, że kontenery IOC ustawiają większość zależności jako statyczne. Cóż, być może niektóre szczegóły implementacji dotyczące ich działania obejmują przechowywanie statyczne (chociaż biorąc pod uwagę, że mają one instancję obiektu takiego jak Ninject IKernel
jako pamięć podstawową, wątpię nawet w to). Ale to nie twoja sprawa!
W rzeczywistości kontener IoC jest tak samo wyraźny, jak zakres wstrzykiwania zależności przez biednego człowieka. (Zamierzam nadal porównywać pojemniki IoC z DI DI biednego człowieka, ponieważ porównywanie ich z brakiem DI byłoby niesprawiedliwe i mylące. I, dla jasności, nie używam „DI biednego człowieka” jako pejoratywnego.)
W DI osoby ubogiej można ręcznie utworzyć zależność, a następnie wstrzyknąć ją do klasy, która jej potrzebuje. W momencie, w którym konstruujesz zależność, wybierasz, co z nią zrobić - przechowuj ją w zmiennej o zasięgu lokalnym, zmiennej klasy, zmiennej statycznej, nie przechowuj jej wcale. Możesz przekazać tę samą instancję do wielu klas lub utworzyć nową dla każdej klasy. Cokolwiek. Chodzi o to, aby zobaczyć, co się dzieje, patrzysz na punkt - prawdopodobnie w pobliżu katalogu głównego aplikacji - gdzie tworzona jest zależność.
A co z kontem IoC? Cóż, robisz dokładnie to samo! Znowu będzie przez terminologii Ninject, obejrzysz gdzie wiązanie jest ustawione, i znaleźć to, czy mówi coś podobnego InTransientScope
, InSingletonScope
albo cokolwiek. Jeśli cokolwiek, jest to potencjalnie wyraźniejsze, ponieważ masz kod deklarujący jego zakres, zamiast konieczności przeglądania metody śledzenia tego, co dzieje się z jakimś obiektem (obiekt może być ograniczony do bloku, ale czy jest używany wielokrotnie w tym na przykład blok lub tylko raz). Więc może odpycha Cię myśl, że musisz użyć funkcji na kontenerze IoC zamiast funkcji surowego języka do dyktowania zakresu, ale tak długo, jak ufasz swojej bibliotece IoC - co powinieneś! - nie ma w tym żadnej realnej wady .
Nadal nie wiem, o czym tu mówisz. Czy kontenery IoC uwzględniają prywatne nieruchomości w ramach ich wewnętrznej implementacji? Nie wiem, dlaczego by to zrobili, ale jeśli tak, to nie twoja sprawa, w jaki sposób implementowana jest biblioteka.
A może ujawniają takie możliwości, jak wstrzykiwanie do prywatnych seterów? Szczerze mówiąc, nigdy tego nie spotkałem i wątpię, czy to naprawdę jest wspólna cecha. Ale nawet jeśli tam jest, jest to prosty przypadek narzędzia, którego można niewłaściwie użyć. Pamiętaj, nawet bez kontenera IoC, to tylko kilka wierszy kodu Refleksji, aby uzyskać dostęp i modyfikować prywatną własność. Jest to coś, czego prawie nigdy nie powinieneś robić, ale to nie znaczy, że .NET jest zły do ujawnienia możliwości. Jeśli ktoś w tak oczywisty i dziki sposób niewłaściwie używa narzędzia, to wina jego osoby, a nie narzędzia.
Ostateczny punkt tutaj jest podobny do 2. W zdecydowanej większości przypadków kontenery IoC używają konstruktora! Iniekcja setera jest oferowana w bardzo szczególnych okolicznościach, gdy z określonych powodów nie można zastosować iniekcji konstruktora. Każdy, kto używa zastrzyku setera przez cały czas, aby ukryć liczbę przekazywanych zależności, masowo wykorzystuje narzędzie. To NIE jest wina narzędzia, tylko ich.
Jeśli byłby to naprawdę łatwy błąd, który mógłby popełnić niewinnie, i który zachęcają kontenery IoC, to dobrze, może miałbyś rację. To byłoby jak upublicznienie każdego członka mojej klasy, a następnie obwinianie innych ludzi, gdy modyfikują rzeczy, których nie powinni, prawda? Ale każdy, kto używa zastrzyku setera do ukrycia naruszeń SRP, umyślnie ignoruje lub całkowicie ignoruje podstawowe zasady projektowania. Nieuzasadnione jest obwinianie tego za kontener IoC.
Jest to szczególnie prawdziwe, ponieważ jest to również coś, co możesz zrobić równie łatwo z DI biednego człowieka:
var myObject
= new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
Tak naprawdę to zmartwienie wydaje się całkowicie ortogonalne względem tego, czy używany jest kontener IoC.
Zmiana sposobu współpracy klas nie stanowi naruszenia OCP. Gdyby tak było, cała inwersja zależności zachęcałaby do naruszenia OCP. Gdyby tak było, nie byliby oboje w tym samym SOLIDNYM akronimie!
Co więcej, ani punkty a), ani b) nie są bliskie powiązania z OCP. Nie wiem nawet, jak odpowiedzieć na te pytania w odniesieniu do OCP.
Jedyne, co mogę zgadnąć, to to, że uważasz, że OCP ma coś wspólnego z zachowaniem, które nie jest zmieniane w czasie wykonywania, ani z tym, gdzie w kodzie kontrolowany jest cykl życia zależności. To nie jest. OCP nie musi modyfikować istniejącego kodu, gdy wymagania są dodawane lub zmieniane. Chodzi o pisanie kodu, a nie o to, jak kod, który już napisałeś, jest sklejany (choć oczywiście luźne sprzęganie jest ważną częścią osiągania OCP).
I ostatnia rzecz, którą mówisz:
Ale nie mogę pokładać takiego samego zaufania w narzędziu strony trzeciej, które zmienia mój skompilowany kod w celu wykonywania obejść rzeczy, których nie byłbym w stanie wykonać ręcznie.
Tak, możesz . Nie ma absolutnie żadnego powodu, aby sądzić, że narzędzia te, na których opiera się ogromna liczba projektów, są bardziej wadliwe lub podatne na nieoczekiwane zachowanie niż jakakolwiek inna biblioteka strony trzeciej.
Uzupełnienie
Właśnie zauważyłem, że w paragrafach wprowadzających można również użyć adresowania. Sardonicznie mówisz, że kontenery IoC „nie stosują jakiejś tajnej techniki, o której nigdy nie słyszeliśmy”, aby uniknąć bałaganu, podatnego na powielanie kodu, aby zbudować wykres zależności. I masz całkowitą rację, to, co robią, polega na rozwiązywaniu tych problemów za pomocą tych samych podstawowych technik, jak zawsze my, programiści.
Pozwól, że opowiem ci o hipotetycznym scenariuszu. Jako programista tworzysz dużą aplikację, aw punkcie wejścia, w którym budujesz wykres obiektowy, zauważasz, że masz dość niechlujny kod. Istnieje wiele klas, które są używane wielokrotnie i za każdym razem, gdy budujesz jedną z nich, musisz budować cały łańcuch zależności pod nimi. Ponadto okazuje się, że nie masz wyraźnego sposobu deklarowania lub kontrolowania cyklu życia zależności, z wyjątkiem niestandardowego kodu dla każdego z nich. Twój kod jest nieustrukturyzowany i pełen powtórzeń. To jest bałagan, o którym mówisz w akapicie wprowadzającym.
Najpierw zaczynasz nieco refaktoryzować - tam, gdzie jakiś powtarzający się kod ma wystarczającą strukturę, wyciągasz go do metod pomocniczych i tak dalej. Ale wtedy zaczynasz myśleć - czy jest to problem, który być może mógłbym rozwiązać w ogólnym sensie, który nie jest specyficzny dla tego konkretnego projektu, ale może pomóc ci we wszystkich twoich przyszłych projektach?
Więc usiądź i pomyśl o tym i zdecyduj, że powinna istnieć klasa, która może rozwiązać zależności. I naszkicujesz, jakich publicznych metod będzie potrzebował:
void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();
Bind
mówi „tam, gdzie widzisz argument typu konstruktora interfaceType
, podaj instancję concreteType
”. Dodatkowy singleton
parametr mówi, czy użyć tego samego wystąpienia za concreteType
każdym razem, czy zawsze tworzyć nowe.
Resolve
po prostu spróbuje zbudować za T
pomocą dowolnego konstruktora, którego może znaleźć, którego argumentami są wszystkie typy, które zostały wcześniej powiązane. Może również wywoływać się rekurencyjnie, aby rozwiązać zależności do samego końca. Jeśli nie może rozwiązać instancji, ponieważ nie wszystko zostało powiązane, zgłasza wyjątek.
Możesz spróbować zaimplementować to sam, a przekonasz się, że potrzebujesz trochę refleksji i trochę buforowania dla powiązań, gdzie singleton
jest to prawda, ale na pewno nic drastycznego lub przerażającego. A kiedy skończysz - voila , masz rdzeń swojego własnego kontenera IoC! Czy to naprawdę takie przerażające? Jedyną prawdziwą różnicą między tym a Ninject lub StructureMap lub Castle Windsor lub czymkolwiek innym jest to, że mają one znacznie więcej funkcji, aby pokryć (wiele!) Przypadków użycia, w których ta podstawowa wersja nie byłaby wystarczająca. Ale w jego sercu znajduje się esencja kontenera IoC.
IOC
iExplicitness of code
jest dokładnie tym, z czym mam problemy. Ręczne DI może łatwo stać się obowiązkiem, ale przynajmniej stawia samowystarczalny, wyraźny przepływ programu w jednym miejscu - „Dostajesz to, co widzisz, widzisz to, co dostajesz”. Podczas gdy powiązania i deklaracje kontenerów MKOl mogą same stać się ukrytym programem równoległym.