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:
- 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.
- 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.
- 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.
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.
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.Odpowiedzi:
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:
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:
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ł:
Bind
mówi „tam, gdzie widzisz argument typu konstruktorainterfaceType
, podaj instancjęconcreteType
”. Dodatkowysingleton
parametr mówi, czy użyć tego samego wystąpienia zaconcreteType
każdym razem, czy zawsze tworzyć nowe.Resolve
po prostu spróbuje zbudować zaT
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.źródło
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
Action
od delegata lub funktora, który nie przyjmuje parametrów i zwracavoid
. Jaka jest odpowiedzialność tej klasy? Nie możesz powiedzieć Mógłbym nazwać tę zależność czymś wyraźnym,CleanUpAction
co 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
Action
konstruktora?” 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.
źródło
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.
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.
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.
Klasa z 12 zależnościami jest prawdopodobnie naruszeniem SRP bez względu na kontekst, w którym ją używasz.
źródło
Słowo
static
kluczowe 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.
źródło
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).
źródło