Kontenery MKOl naruszają zasady OOP


51

Jaki jest cel kontenerów MKOl? Połączone powody można uprościć w następujący sposób:

Podczas korzystania z zasad programistycznych OOP / SOLID, wstrzykiwanie zależności jest nieporządne. Albo masz punkty wejścia najwyższego poziomu, które zarządzają zależnościami dla wielu poziomów poniżej siebie i przekazują zależności rekurencyjnie przez konstrukcję, albo masz nieco zduplikowany kod we wzorcach fabryk / konstruktorów i interfejsach, które budują zależności według potrzeb.

Nie ma metody OOP / SOLID, aby to wykonać ORAZ mieć bardzo ładny kod.

Jeśli to poprzednie stwierdzenie jest prawdziwe, to jak to zrobić Kontenery IOC? O ile mi wiadomo, nie stosują jakiejś nieznanej techniki, której nie można wykonać przy użyciu ręcznego DI. Jedynym wyjaśnieniem jest to, że kontenery IOC łamią zasady OOP / SOLID poprzez użycie prywatnych obiektów statycznych obiektów statycznych.

Czy kontenery IOC łamią następujące zasady za kulisami? To jest prawdziwe pytanie, ponieważ mam dobre zrozumienie, ale mam wrażenie, że ktoś inny ma lepsze zrozumienie:

  1. Kontrola zakresu . Zakres jest przyczyną prawie każdej decyzji, którą podejmuję w sprawie projektu kodu. Blok, lokalny, moduł, statyczny / globalny. Zakres powinien być bardzo wyraźny, jak najwięcej na poziomie bloku, a jak najmniej na poziomie statycznym, jak to możliwe. Powinieneś zobaczyć deklaracje, instancje i zakończenia cyklu życia. Ufam, że język i GC zarządzają zasięgiem, o ile wyrażę na to zgodę. W moich badaniach odkryłem, że IOC Containers ustawia większość lub wszystkie zależności jako statyczne i kontroluje je za pomocą implementacji AOP za kulisami. Więc nic nie jest przezroczyste.
  2. Kapsułkowanie . Jaki jest cel enkapsulacji? Dlaczego powinniśmy zatrzymać prywatnych członków? Ze względów praktycznych jest tak, że implementatorzy naszego API nie mogą przerwać implementacji poprzez zmianę stanu (którym powinna zarządzać klasa właściciela). Ale również ze względów bezpieczeństwa nie może dojść do zastrzyków, które wyprzedzają nasze państwa członkowskie i omijają walidację i kontrolę klasy. Zatem wszystko (frameworki lub frameworki IOC), które w jakiś sposób wstrzykuje kod przed czasem kompilacji, aby umożliwić zewnętrzny dostęp do prywatnych członków, jest dość ogromne.
  3. Zasada pojedynczej odpowiedzialności . Na powierzchni pojemniki IOC wydają się czyścić rzeczy. Ale wyobraź sobie, jak osiągnąłbyś to samo bez ram pomocniczych. Miałbyś konstruktorów z kilkunastoma zależnościami przekazanymi. To nie znaczy, że zakryjesz je kontenerami IOC, to dobrze! Jest to znak, aby ponownie rozdzielić swój kod i postępować zgodnie z SRP.
  4. Otwarty / zamknięty . Podobnie jak SRP nie jest tylko dla klasy (stosuję SRP do linii pojedynczej odpowiedzialności, nie mówiąc już o metodach). Otwarte / zamknięte to nie tylko teoria wysokiego poziomu, aby nie zmieniać kodu klasy. Jest to praktyka rozumienia konfiguracji programu i kontrolowania tego, co się zmienia, a co się rozszerza. Kontenery IOC mogą częściowo zmienić sposób, w jaki działają twoje klasy, ponieważ:

    • za. Główny kod nie określa determinacji wyłączania zależności, konfiguracja frameworku jest.

    • b. Zakres może zostać zmieniony w czasie, który nie jest kontrolowany przez członków wywołujących, jest natomiast określany zewnętrznie przez statyczny szkielet.

Tak więc konfiguracja klasy nie jest naprawdę zamknięta, zmienia się ona w zależności od konfiguracji narzędzia innej firmy.

To pytanie jest spowodowane tym, że niekoniecznie jestem panem wszystkich kontenerów MKOl. I chociaż pomysł na kontener MKOl jest fajny, wydaje się, że to tylko fasada, która zakrywa słabe wdrożenie. Ale w celu uzyskania zewnętrznej kontroli zakresu, dostępu do prywatnego członka i leniwego ładowania, musi trwać wiele nietrywialnych wątpliwych rzeczy. AOP jest świetny, ale sposób, w jaki osiąga się go za pomocą kontenerów IOC, jest również wątpliwy.

Mogę zaufać C # i .NET GC, że zrobię to, czego oczekuję. 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.

EG: Entity Framework i inne ORM tworzą silnie typowane obiekty i mapują je na jednostki bazy danych, a także zapewniają podstawową funkcjonalność płyty kotłowej do wykonywania CRUD. Każdy może zbudować własną ORM i nadal ręcznie przestrzegać zasad OOP / SOLID. Ale te ramy pomagają nam, więc nie musimy za każdym razem wymyślać od nowa koła. Podczas gdy pojemniki IOC, jak się wydaje, pomagają nam celowo obejść zasady OOP / SOLID i zatuszować to.


13
+1 Korelacja pomiędzy IOCi Explicitness of codejest 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.
Eugene Podskal

6
Jak to w ogóle pytanie?
Stephen

8
To bardziej przypomina post na blogu niż pytanie.
Bart van Ingen Schenau

4
Mam wewnętrzną kategorię pytań „zbyt kłótliwą”, która często nie jest formalnym bliskim powodem, ale głosuję za nią, a czasem nawet głosuję za jej zamknięciem „nie jest to prawdziwe pytanie”. Ma jednak dwa aspekty, a to pytanie spełnia tylko pierwsze. (1) Pytanie zawiera tezę i pyta, czy jest poprawne, (2) pytający odpowiada na odpowiedzi, które się nie zgadzają, broniąc pozycji wyjściowej. Odpowiedź Matthew zaprzecza niektórym stwierdzeniom tego pytania, więc jeśli pytający nie przeczeka, że ​​osobiście zaliczę to pytanie w dobrej wierze, aczkolwiek forma „Mam rację, prawda?”
Steve Jessop,

3
@BenAaronson Dziękuję Ben. Staram się przekazać to, czego się nauczyłem podczas swoich badań i uzyskać odpowiedzi na temat natury kontenerów MKOl. W wyniku moich badań odkryłem, że zasady te zostały złamane. Najlepsza odpowiedź na moje pytanie potwierdzi lub zaprzeczy, że pojemniki IOC mogą w jakiś sposób robić rzeczy, których nie możemy zrobić ręcznie, bez łamania zasad OOP / SOLID. Z powodu waszych wspaniałych komentarzy, przełożyłem moje pytanie na więcej przykładów.
Suamere

Odpowiedzi:


35

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:

  1. 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 IKerneljako 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, InSingletonScopealbo 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 .

  2. 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.

  3. 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.

  4. 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>();

Bindmówi „tam, gdzie widzisz argument typu konstruktora interfaceType, podaj instancję concreteType”. Dodatkowy singletonparametr mówi, czy użyć tego samego wystąpienia za concreteTypekażdym razem, czy zawsze tworzyć nowe.

Resolvepo prostu spróbuje zbudować za Tpomocą 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 singletonjest 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.


Dzięki Ben. Szczerze mówiąc, pracuję z IOC Containers dopiero od około roku i chociaż tego nie robię, tutoriale i rówieśnicy wokół mnie naprawdę koncentrują się na zastrzykach własności (w tym prywatnych). To szalone i przywołuje wszystkie punkty, które robię. Ale nawet jeśli inni znajdują się w takiej sytuacji, myślę, że najlepszą rzeczą, jaką mogą zrobić, to dowiedzieć się więcej o tym, w jaki sposób kontenery IOC powinny działać zgodnie z zasadami, i wskazać tych łamaczy zasad w recenzjach kodu. Jednak ... (ciąg dalszy)
Suamere

Moje największe obawy niekoniecznie są wymienione w zasadach OOP / SOLID, a to jest jednoznaczność zakresu. Nadal wyrzucamy zakres blokowy, lokalny, modułowy i globalny, rozumiejąc przez okno słowa kluczowego. Cały pomysł używania bloków i deklaracji określających, kiedy coś wykracza poza zakres lub zostaje sprzedany, jest dla mnie ogromny i myślę, że to była najstraszniejsza część kontenerów IOC, aby zastąpić ten bardzo podstawowy element rozwoju słowem kluczowym dotyczącym rozszerzenia .
Suamere

Tak więc oznaczyłem twoją odpowiedź jako odpowiedź, ponieważ naprawdę odnosi się do sedna tego. Z tego, co przeczytałem, musisz zaufać temu, co robią Pojemniki MKOl pod przykryciem, bo wszyscy inni tak robią. A jeśli chodzi o możliwości ewentualnego niewłaściwego użycia i elegancki sposób zakrycia go, to martwi się sklepem i recenzjami kodu i powinien dołożyć należytej staranności, aby postępować zgodnie z zasadami dobrego rozwoju.
Suamere

2
W odniesieniu do 4, wstrzykiwanie zależności nie występuje w akronimie SOLID . „D” = „Zasada inwersji zależności”, która jest całkowicie niepowiązanym pojęciem.
astreltsov

@astr Dobra uwaga. „Zastrzyk” zastąpiłem „odwróceniem”, ponieważ myślę, że właśnie to chciałem napisać. To wciąż trochę nadmierne uproszczenie, ponieważ inwersja zależności niekoniecznie oznacza wiele konkretnych implementacji, ale myślę, że znaczenie jest wystarczająco jasne. Byłoby niebezpiecznie polegać na polimorficznej abstrakcji, gdyby wyłączenie implementacji było naruszeniem OCP.
Ben Aaronson

27

Czy kontenery MKOl nie łamią wielu z tych zasad?

W teorii? Nie. Kontenery IoC to tylko narzędzie. Możesz mieć obiekty, które mają jedną odpowiedzialność, dobrze oddzielone interfejsy, nie można modyfikować ich zachowania po napisaniu itp.

W praktyce? Absolutnie.

To znaczy, słyszałem, że niewielu programistów mówi, że używają kontenerów IoC specjalnie po to, aby umożliwić ci konfigurację w celu zmiany zachowania systemu - co narusza zasadę Open / Closed. Kiedyś żartowano na temat kodowania w XML, ponieważ 90% ich zadań polegało na naprawianiu konfiguracji Spring. I z pewnością masz rację, że ukrywanie zależności (co prawda nie wszystkie pojemniki IoC robią) jest szybkim i łatwym sposobem na stworzenie bardzo sprzężonego i bardzo delikatnego kodu.

Spójrzmy na to z innej strony. Rozważmy bardzo podstawową klasę, która ma jedną, podstawową zależność. Zależy to Actionod delegata lub funktora, który nie przyjmuje parametrów i zwraca void. Jaka jest odpowiedzialność tej klasy? Nie możesz powiedzieć Mógłbym nazwać tę zależność czymś wyraźnym, CleanUpActionco sprawia, że ​​myślisz, że robi to, co chcesz. Gdy tylko IoC wejdzie do gry, stanie się czymkolwiek . Każdy może przejść do konfiguracji i zmodyfikować oprogramowanie, aby wykonywać dowolne czynności.

„Czym różni się to od przekazania Actionkonstruktora?” ty pytasz. Jest inaczej, ponieważ nie można zmienić tego, co jest przekazywane do konstruktora po wdrożeniu oprogramowania (lub w czasie wykonywania!). Jest inaczej, ponieważ testy jednostkowe (lub testy konsumenckie) obejmowały scenariusze przekazania delegata do konstruktora.

„Ale to fałszywy przykład! Moje rzeczy nie pozwalają na zmianę zachowania!” wołasz. Przykro mi to mówić, ale tak jest. Chyba że interfejs, który wstrzykujesz, to tylko dane. A twój interfejs to nie tylko dane, ponieważ gdyby były to tylko dane, budowałbyś prosty obiekt i korzystał z niego. Gdy tylko masz wirtualną metodę w swoim punkcie iniekcji, kontrakt twojej klasy używającej IoC brzmi: „Mogę zrobić wszystko ”.

„Jak to w ogóle różni się od używania skryptów do kierowania rzeczami? To jest w porządku.” niektórzy z was pytają. Nie jest inaczej. Z perspektywy projektowania programu nie ma różnicy. Wychodzisz ze swojego programu, aby uruchomić dowolny kod. I na pewno zostało to zrobione w grach od wieków. Odbywa się to w tonach narzędzi do administrowania systemami. Czy to OOP? Nie całkiem.

Nie ma usprawiedliwienia dla ich obecnej wszechobecności. Kontenery IoC mają jeden solidny cel - sklejenie twoich zależności, gdy masz ich tak wiele kombinacji, lub zmieniają się tak szybko, że niepraktyczne jest wyrażanie tych zależności. Jednak bardzo niewiele firm faktycznie pasuje do tego scenariusza.


3
Dobrze. I wiele sklepów twierdzi, że ich produkt jest tak złożony, że należą do tej kategorii, a tak naprawdę nie. Taka jest prawda w wielu scenariuszach.
Suamere

12
Ale IoC jest dokładnym przeciwieństwem tego, co powiedziałeś. Wykorzystuje Open / Closeness programu. Jeśli możesz zmienić zachowanie całego programu bez konieczności zmiany samego kodu.
Euphoric

6
@Euphoric - otwarte dla rozszerzenia, zamknięte dla modyfikacji. Jeśli możesz zmienić zachowanie całego programu, nie jest on zamknięty dla modyfikacji, prawda? Konfiguracja IoC nadal jest twoim oprogramowaniem, nadal jest kodem używanym przez twoje klasy do pracy - nawet jeśli jest w XML, a nie w Javie, C # lub czymkolwiek.
Telastyn

4
@Telastyn „Zamknięty dla modyfikacji” oznacza, że ​​zmiana (modyfikacja) kodu nie powinna być konieczna, aby zmienić zachowanie całości. W dowolnej konfiguracji zewnętrznej możesz po prostu wskazać nową bibliotekę DLL i zachować zachowanie całej zmiany.
Euphoric

12
@Telastyn Nie tak mówi zasada otwartego / zamkniętego. W przeciwnym razie metoda pobierająca parametry naruszyłaby OCP, ponieważ mógłbym „zmodyfikować” jej zachowanie, przekazując mu inne parametry. Podobnie w twojej wersji OCP byłoby w bezpośrednim konflikcie z DI, co spowodowałoby, że dziwne byłoby, gdyby oba pojawiały się w akronimie SOLID.
Ben Aaronson,

14

Czy korzystanie z kontenera IoC musi naruszać zasady OOP / SOLID? Nie .

Czy możesz używać kontenerów IoC w taki sposób, że naruszają zasady OOP / SOLID? Tak .

Kontenery IoC to po prostu ramy do zarządzania zależnościami w aplikacji.

Powiedziałeś, że Leniwe zastrzyki, ustawiacze właściwości i kilka innych funkcji, których jeszcze nie odczułem potrzeby użycia, z którymi się zgadzam, mogą dać ci mniej niż optymalny projekt. Jednak zastosowania tych konkretnych funkcji wynikają z ograniczeń technologii, w której implementujesz kontenery IoC.

Na przykład formularze ASP.NET WebForms są wymagane do użycia wstrzykiwania właściwości, ponieważ nie można utworzyć wystąpienia Page. Jest to zrozumiałe, ponieważ formularze WebForm nigdy nie zostały zaprojektowane z uwzględnieniem ścisłych paradygmatów OOP.

Z drugiej strony ASP.NET MVC jest całkowicie możliwe użycie kontenera IoC przy użyciu bardzo przyjaznego dla OOP / SOLIDa wstrzykiwania konstruktora.

W tym scenariuszu (przy użyciu wstrzykiwania konstruktora) uważam, że promuje zasady SOLID z następujących powodów:

  • Zasada pojedynczej odpowiedzialności - Posiadanie wielu mniejszych klas pozwala być bardzo szczegółowymi i mieć jeden powód istnienia. Korzystanie z kontenera IoC znacznie ułatwia ich organizowanie.

  • Otwarte / zamknięte - w tym miejscu naprawdę wyróżnia się użycie kontenera IoC, można rozszerzyć funkcjonalność klasy poprzez wstrzykiwanie różnych zależności. Wstrzykiwanie zależności pozwala modyfikować zachowanie tej klasy. Dobrym przykładem jest zmiana klasy, którą wstrzykujesz, pisząc dekorator, aby powiedzieć, że dodaj buforowanie lub rejestrowanie. Możesz całkowicie zmienić implementacje swoich zależności, jeśli są napisane na odpowiednim poziomie abstrakcji.

  • Zasada substytucji Liskowa - tak naprawdę nie dotyczy

  • Zasada segregacji interfejsu - Możesz przypisać wiele interfejsów do jednego konkretnego czasu, jeśli chcesz, każdy pojemnik IoC warty ziarna soli łatwo to zrobi.

  • Inwersja zależności - kontenery IoC pozwalają łatwo wykonać inwersję zależności.

Definiujesz kilka zależności i deklarujesz relacje i zakresy oraz bing bang boom: masz magiczny dodatek, który w pewnym momencie zmienia kod lub IL i sprawia, że ​​wszystko działa. To nie jest wyraźne ani przejrzyste.

Jest to zarówno jawne, jak i przejrzyste, musisz powiedzieć swojemu kontenerowi IoC, jak połączyć zależności i jak zarządzać czasem życia obiektu. Może to być konwencja, pliki konfiguracyjne lub kod zapisany w głównych aplikacjach systemu.

Zgadzam się, że kiedy piszę lub czytam kod przy użyciu kontenerów IOC, pozbywa się nieco nieeleganckiego kodu.

Jest to cały powód, dla którego OOP / SOLID umożliwia zarządzanie systemami. Korzystanie z kontenera IoC jest zgodne z tym celem.

Autor tej klasy mówi: „Gdybyśmy nie użyli kontenera MKOl, Konstruktor miałby tuzin parametrów! To okropne!”

Klasa z 12 zależnościami jest prawdopodobnie naruszeniem SRP bez względu na kontekst, w którym ją używasz.


2
Doskonała odpowiedź. Dodatkowo myślę, że wszystkie główne kontenery IOC obsługują również AOP, co może bardzo pomóc w OCP. W przypadku problemów przekrojowych AOP jest zwykle bardziej przyjazną dla OCP trasą niż Dekorator.
Ben Aaronson,

2
@BenAaronson Biorąc pod uwagę, że AOP wstrzykuje kod do klasy, nie nazwałbym go bardziej przyjaznym dla OCP. Siłą otwierasz i zmieniasz klasę podczas kompilacji / środowiska wykonawczego.
Doval,

1
@Doval Nie rozumiem, jak wstrzykuje kod do klasy. Uwaga Mówię o stylu Castle DynamicProxy, w którym masz przechwytywacz, a nie o stylu tkania IL. Ale przechwytywacz jest w zasadzie tylko dekoratorem, który wykorzystuje pewne odbicie, aby nie musiał się łączyć z określonym interfejsem.
Ben Aaronson,

„Inwersja zależności - kontenery IoC pozwalają łatwo wykonać inwersję zależności” - nie, po prostu wykorzystują coś, co jest korzystne, niezależnie od tego, czy masz IoC, czy nie. To samo z innymi zasadami.
Den

Naprawdę nie rozumiem logiki wyjaśnienia, że ​​coś nie jest złe, ponieważ jest to struktura. ServiceLocators są również tworzone jako frameworki i są uważane za złe :).
ipavlu,

5

Czy kontenery IOC nie psują się i nie pozwalają koderom na łamanie, wiele zasad OOP / SOLID?

Słowo statickluczowe w języku C # pozwala programistom łamać wiele zasad OOP / SOLID, ale nadal jest ważnym narzędziem w odpowiednich okolicznościach.

Kontenery IoC pozwalają na dobrze ustrukturyzowany kod i zmniejszają obciążenie poznawcze dla programistów. Kontener IoC może być użyty, aby znacznie ułatwić przestrzeganie zasad SOLID. Oczywiście, można go niewłaściwie wykorzystać, ale gdybyśmy wykluczyli użycie jakiegokolwiek narzędzia, które może być niewłaściwie użyte, musielibyśmy całkowicie zrezygnować z programowania.

Tak więc, chociaż ta reguła podnosi pewne ważne uwagi na temat źle napisanego kodu, nie jest to dokładnie pytanie i nie jest dokładnie przydatna.


Rozumiem, że IOC Containers, aby zarządzać zasięgiem, lenistwem i dostępnością, zmienia skompilowany kod, aby robić rzeczy, które łamią OOP / SOLID, ale ukrywa tę implementację w ładnych ramach. Ale tak, to dodatkowo otwiera inne drzwi do niewłaściwego użytkowania. Najlepszą odpowiedzią na moje pytanie byłoby wyjaśnienie, w jaki sposób wdrażane są pojemniki MKOl, które dodają lub negują moje własne badania.
Suamere

2
@ Suamere - Myślę, że używasz zbyt szerokiej definicji MKOl. Ten, którego używam, nie modyfikuje skompilowanego kodu. Istnieje wiele innych narzędzi spoza MKOl, które również modyfikują skompilowany kod. Może twój tytuł powinien być bardziej podobny do: „Czy modyfikowanie skompilowanego kodu łamie ...”.
Cerad,

@ Suamere Nie jestem tak doświadczony z IoC, ale nigdy nie słyszałem o IoC zmieniającym skompilowany kod. Czy możesz podać informacje o platformach / językach / platformach, o których mówisz?
Euphoric

1
„Kontener IoC może być użyty, aby znacznie ułatwić przestrzeganie zasad SOLID”. - czy możesz opracować, proszę? Pomocne byłoby wyjaśnienie, w jaki sposób pomaga to w każdej zasadzie. I oczywiście wykorzystanie czegoś nie jest tym samym, co pomaganie, aby coś się wydarzyło (np. DI).
Den

1
@Euphoric Nie wiem o frameworkach .net, o których mówił Suamere, ale Spring z pewnością dokonuje modyfikacji kodu pod maską. Najbardziej oczywistym przykładem jest obsługa iniekcji opartej na metodach (przy czym zastępuje ona metodę abstrakcyjną w klasie, aby zwrócić zależność, którą zarządza). W dużym stopniu korzysta również z automatycznie generowanych serwerów proxy w celu zawijania kodu w celu zapewnienia dodatkowych zachowań (np. Do automatycznego zarządzania transakcjami). Jednak wiele z tego nie jest tak naprawdę związanych z jego rolą jako frameworka DI.
Jules

3

W twoim pytaniu widzę wiele błędów i nieporozumień. Pierwszym z głównych jest brak rozróżnienia między iniekcją nieruchomości / pola, iniekcją konstruktora i lokalizatorem usług. Odbyło się wiele dyskusji (np. Flamewars) na temat tego, który sposób jest właściwy. W swoim pytaniu wydaje się, że zakładasz tylko zastrzyk własności i ignorujesz zastrzyk konstruktora.

Po drugie, zakładasz, że kontenery IoC w jakiś sposób wpływają na projekt twojego kodu. Nie używają, a przynajmniej nie powinna być konieczna zmiana projektu kodu, jeśli używasz IoC. Twoje problemy z klasami z wieloma konstruktorami to przypadek ukrywania przez IoC prawdziwych problemów z twoim kodem (najprawdopodobniej klasa, która łamie SRP), a ukryte zależności są jednym z powodów, dla których ludzie odradzają korzystanie z zastrzyku własności i lokalizatora usług.

Nie rozumiem, skąd bierze się problem związany z zasadą otwartego zamknięcia, więc nie będę tego komentował. Brakuje jednak jednej części SOLID, która ma duży wpływ na to: zasada inwersji zależności. Jeśli zastosujesz się do DIP, przekonasz się, że odwrócenie zależności wprowadza abstrakcję, której implementacja musi zostać rozwiązana, gdy zostanie utworzona instancja klasy. Dzięki takiemu podejściu uzyskasz wiele klas, które wymagają realizacji wielu interfejsów i zapewnienia prawidłowego funkcjonowania podczas budowy. Jeśli miałbyś następnie ręcznie podać te zależności, musiałbyś wykonać wiele dodatkowej pracy. Kontenery IoC ułatwiają wykonanie tej pracy, a nawet pozwalają na jej zmianę bez konieczności ponownej kompilacji programu.

Tak więc odpowiedź na twoje pytanie brzmi „nie”. Kontenery IoC nie łamią zasad projektowania OOP. Przeciwnie, rozwiązują problemy spowodowane przez przestrzeganie zasad SOLID (szczególnie zasada Zależności-Inwersji).


W ostatnim czasie próbowałem zastosować IoC (Autofac) do nietrywialnego projektu. Uświadomiłem sobie, że musiałem wykonać podobną pracę z konstrukcjami niejęzykowymi (new () vs API): określając, co należy rozwiązać w interfejsach, a co w konkretnych klasach, określając, co powinno być instancją, a co singletonem, upewniając się, że zastrzyk własności działa poprawnie, aby złagodzić zależność cykliczną. Postanowiłem porzucić. Działa dobrze z kontrolerami w ASP MVC.
Den

@ Den Twój projekt nie został oczywiście zaprojektowany z myślą o inwersji zależności. I nigdzie nie powiedziałem, że korzystanie z IoC jest banalne.
Euphoric

Właściwie o to chodzi - od samego początku korzystam z Inwersji Zależności. IoC wpływające na projekt poza tym jest moim największym zmartwieniem. Na przykład dlaczego miałbym używać interfejsów wszędzie, aby uprościć IoC? Zobacz także najwyższy komentarz do pytania.
Den

@ Czy technicznie nie potrzebujesz „używać” interfejsów? Interfejsy pomagają w kpinach podczas testów jednostkowych. Możesz także oznaczyć rzeczy, których potrzebujesz, aby kpić z wirtualnego.
Fred
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.