Obecnie próbuję znaleźć SOLID. Zatem zasada inwersji zależności oznacza, że dowolne dwie klasy powinny komunikować się za pośrednictwem interfejsów, a nie bezpośrednio. Przykład: Jeśli class A
ma metodę, która oczekuje wskaźnika do obiektu typu class B
, wówczas metoda ta powinna faktycznie oczekiwać obiektu typu abstract base class of B
. Pomaga to również w otwarciu / zamknięciu.
Pod warunkiem, że dobrze to zrozumiałem, moim pytaniem byłoby, czy dobrą praktyką jest stosowanie tego do wszystkich interakcji klasowych, czy powinienem próbować myśleć w kategoriach warstwowych ?
Sceptycznie podchodzę do tego dlatego, że płacimy pewną cenę za przestrzeganie tej zasady. Powiedz, że muszę zaimplementować funkcję Z
. Po analizie wniosku, że funkcja Z
składa się z funkcjonalnością A
, B
a C
. Tworzę fasady klasa Z
, że poprzez interfejsy, wykorzystuje klas A
, B
i C
. Zaczynam kodowanie wdrażanie i w pewnym momencie sprawę, że zadanie Z
faktycznie składa się z funkcjonalnością A
, B
a D
. Teraz muszę usunąć C
interfejs, C
prototyp klasy i napisać osobny D
interfejs i klasę. Bez interfejsów tylko klasa musiałaby zostać wymieniona.
Innymi słowy, aby coś zmienić, muszę zmienić 1. dzwoniącego 2. interfejs 3. deklarację 4. implementację. W implementacji bezpośrednio sprzężonej z pytonem musiałbym zmienić tylko implementację.
Odpowiedzi:
W wielu kreskówkach lub innych mediach siły dobra i zła są często ilustrowane przez anioła i demona siedzącego na ramionach postaci. W naszej historii, zamiast dobra i zła, mamy SOLID na jednym ramieniu, a YAGNI (Nie będziesz go potrzebował!) Na drugim.
Maksymalne zasady SOLID najlepiej nadają się do dużych, złożonych, ultra konfigurowalnych systemów dla przedsiębiorstw. W przypadku mniejszych lub bardziej specyficznych systemów nie jest właściwe, aby wszystko było absurdalnie elastyczne, ponieważ czas spędzony na abstrakcji nie okaże się korzystny.
Przekazywanie interfejsów zamiast konkretnych klas czasami oznacza na przykład, że możesz łatwo zamienić odczyt z pliku na strumień sieciowy. Jednak w przypadku dużej liczby projektów oprogramowania taka elastyczność nigdy nie będzie potrzebna. Równie dobrze możesz po prostu przekazać konkretne klasy plików i nazwać to dniem i oszczędzić komórki mózgowe.
Częścią sztuki tworzenia oprogramowania jest dobre wyczucie tego, co może się zmienić w miarę upływu czasu, a co nie. Do rzeczy, które mogą się zmienić, użyj interfejsów i innych pojęć SOLID. Do rzeczy, które nie będą, użyj YAGNI i po prostu podaj konkretne typy, zapomnij o klasach fabrycznych, zapomnij o podłączaniu i konfiguracji środowiska wykonawczego itp. I zapomnij o wielu abstrakcjach SOLID. Z mojego doświadczenia wynika, że podejście YAGNI okazało się być poprawne znacznie częściej niż nie.
źródło
File
, więc zamiast tego zajmie się wykonaniemIFile
zadania”. Wówczas nie mogą łatwo zastąpić strumienia sieciowego, ponieważ przesadzili z interfejsem, a sąIFile
metody, których metoda nawet nie używa, które nie dotyczą gniazd, więc gniazdo nie może zaimplementowaćIFile
. Jedną z rzeczy, dla których DI nie jest srebrną kulą, jest wymyślenie odpowiednich abstrakcji (interfejsów) :-)Słowami laika:
Stosowanie DIP jest łatwe i przyjemne . Niepoprawne wykonanie projektu przy pierwszej próbie nie jest wystarczającym powodem rezygnacji z DIP.
Z drugiej strony programowanie z interfejsami i OOD może przywrócić radość czasami nieaktualnemu rzemiosłu programowania.
Niektórzy twierdzą, że to dodaje złożoności, ale myślę, że opossite jest prawdą. Nawet w przypadku małych projektów. Ułatwia testowanie / kpiny. To sprawia, że twój kod ma mniej, jeśli jakieś
case
instrukcje lub są zagnieżdżoneifs
. Zmniejsza złożoność cykliczną i sprawia, że myślisz na nowo. Dzięki temu programowanie jest bardziej podobne do projektowania i produkcji w świecie rzeczywistym.źródło
Użyj inwersji zależności tam, gdzie ma to sens.
Jednym z przeciwnych przykładów jest klasa „string” zawarta w wielu językach. Reprezentuje prymitywną koncepcję, zasadniczo tablicę znaków. Zakładając, że możesz zmienić tę klasę podstawową, nie ma sensu używać DI tutaj, ponieważ nigdy nie będziesz musiał zamieniać stanu wewnętrznego na coś innego.
Jeśli masz grupę obiektów używanych wewnętrznie w module, które nie są narażone na inne moduły lub ponownie użyte gdziekolwiek, prawdopodobnie nie warto używać DI.
Są dwa miejsca, w których moim zdaniem powinno się automatycznie stosować DI:
W modułach przeznaczonych do rozbudowy. Jeśli głównym celem modułu jest jego rozbudowa i zmiana zachowania, sensowne jest, aby piec DI od samego początku.
W modułach, które refaktoryzujesz w celu ponownego użycia kodu. Być może kodujesz klasę, aby coś zrobić, a potem uświadomisz sobie, że dzięki refaktorowi możesz wykorzystać ten kod gdzie indziej i trzeba to zrobić . To świetny kandydat na DI i inne zmiany rozszerzalności.
Klucze tutaj są używane tam, gdzie jest to potrzebne, ponieważ wprowadzi dodatkową złożoność i upewnij się, że mierzysz to za pomocą wymagań technicznych (punkt pierwszy) lub ilościowego przeglądu kodu (punkt drugi).
DI jest doskonałym narzędziem, ale jak każde * narzędzie, może być nadużywane lub niewłaściwie używane.
* Wyjątek od powyższej zasady: piła szablasta to idealne narzędzie do każdego zadania. Jeśli to nie rozwiąże problemu, usunie go. Na stałe.
źródło
String
typu, który nie jest rozszerzalny dla użytkownika , istnieje wiele przypadków, w których alternatywne reprezentacje byłyby pomocne, gdyby typ miał dobry zestaw operacji wirtualnych (np. Skopiowanie podłańcucha do określonej częścishort[]
, raport, czy podłańcuch zawiera lub może zawierać tylko ASCII, spróbuj skopiować podciąg, który prawdopodobnie zawiera tylko ASCII, do określonej częścibyte[]
itp.) Szkoda, że struktury nie mają zaimplementowanych użytecznych interfejsów.Wydaje mi się, że w pierwotnym pytaniu brakuje części sensu DIP.
Aby naprawdę skorzystać z DIP, należy najpierw utworzyć klasę Z i wywołać funkcjonalność klas A, B i C (które nie zostały jeszcze opracowane). Daje to interfejs API dla klas A, B i C. Następnie przechodzisz do tworzenia klas A, B i C i wypełniasz szczegóły. Powinieneś skutecznie tworzyć abstrakcje, których potrzebujesz, tworząc klasę Z, całkowicie w oparciu o to, czego potrzebuje klasa Z. Możesz nawet pisać testy w klasie Z, zanim jeszcze klasy A, B lub C zostaną napisane.
Pamiętaj, że DIP mówi, że „Moduły wysokiego poziomu nie powinny zależeć od modułów niskiego poziomu. Oba powinny zależeć od abstrakcji”.
Po ustaleniu, czego potrzebuje klasa Z i sposobie, w jaki chce uzyskać to, czego potrzebuje, możesz wypełnić szczegóły. Oczywiście, czasem trzeba będzie wprowadzić zmiany w klasie Z, ale w 99% przypadków tak nie będzie.
Nigdy nie będzie klasy D, ponieważ doszedłeś do wniosku, że Z potrzebuje A, B i C, zanim zostaną napisane. Zmiana wymagań to zupełnie inna historia.
źródło
Krótka odpowiedź brzmi „prawie nigdy”, ale w rzeczywistości jest kilka miejsc, w których DIP nie ma sensu:
Fabryki lub budowniczych, których zadaniem jest tworzenie obiektów. Są to zasadniczo „węzły liści” w systemie, który w pełni obejmuje IoC. W pewnym momencie coś musi faktycznie stworzyć twoje obiekty i nie może zależeć od niczego innego, aby to zrobić. W wielu językach kontener IoC może to zrobić za Ciebie, ale czasami musisz to zrobić w staromodny sposób.
Implementacje struktur danych i algorytmów. Zasadniczo w tych przypadkach istotne cechy, które optymalizujesz (takie jak czas działania i złożoność asymptotyczna), zależą od używanych typów danych. Jeśli wdrażasz tabelę skrótów, naprawdę musisz wiedzieć, że pracujesz z tablicą do przechowywania, a nie z listą połączoną, a tylko sama tabela wie, jak poprawnie przydzielić tablice. Nie chcesz także przekazywać zmiennej tablicy, a osoba dzwoniąca złamie tablicę skrótów, bawiąc się jej zawartością.
Klasy modeli domen . Wprowadzają one logikę biznesową i (przez większość czasu) sensowne jest mieć tylko jedną implementację, ponieważ (przez większość czasu) tworzysz oprogramowanie tylko dla jednej firmy. Chociaż niektóre klasy modeli domen mogą być konstruowane przy użyciu innych klas modeli domen, zazwyczaj dzieje się tak w poszczególnych przypadkach. Ponieważ obiekty modelu domeny nie zawierają żadnej funkcji, którą można by z powodzeniem wyśmiewać, DIP nie daje korzyści w zakresie testowania ani konserwacji.
Wszelkie obiekty, które są dostarczane jako zewnętrzny interfejs API i muszą tworzyć inne obiekty, których szczegółów implementacji nie chcesz udostępniać publicznie. Obejmuje to ogólną kategorię „projektowanie bibliotek różni się od projektowania aplikacji”. Biblioteka lub framework może swobodnie korzystać z DI wewnętrznie, ale w końcu będzie musiał wykonać jakąś rzeczywistą pracę, w przeciwnym razie nie będzie to bardzo przydatna biblioteka. Powiedzmy, że tworzysz bibliotekę sieciową; tak naprawdę nie chcesz, aby konsument mógł zapewnić własną implementację gniazda. Możesz użyć wewnętrznie abstrakcji gniazda, ale API, które udostępniasz dzwoniącym, stworzy własne gniazda.
Testy jednostkowe i podwójne testy. Podróbki i odcinki powinny zrobić jedną rzecz i zrobić to po prostu. Jeśli masz fałszywkę, która jest wystarczająco złożona, aby martwić się o to, czy zrobić wstrzyknięcie zależności, to prawdopodobnie jest zbyt złożona (być może dlatego, że implementuje interfejs, który jest również zbyt skomplikowany).
Może być więcej; są to te, które widzę dość często.
źródło
Niektóre znaki, że możesz stosować DIP na zbyt mikro poziomie, gdzie nie zapewnia to wartości:
Jeśli to właśnie widzisz, być może lepiej będzie, jeśli Z zadzwoni bezpośrednio do C i pominie interfejs.
Nie myślę też o dekorowaniu metod za pomocą szkieletu wstrzykiwania zależności / dynamicznego proxy (Spring, Java EE) w taki sam sposób, jak w prawdziwym SOLID DIP - jest to bardziej szczegół implementacji sposobu, w jaki dekoracja metody działa w tym stosie technologicznym. Społeczność Java EE uważa to za ulepszenie polegające na tym, że nie potrzebujesz par Foo / FooImpl, tak jak kiedyś ( odniesienie ). Natomiast Python obsługuje dekorację funkcji jako pierwszorzędną funkcję językową.
Zobacz także ten post na blogu .
źródło
Jeśli zawsze odwracasz swoje zależności, to wszystkie twoje zależności są do góry nogami. Co oznacza, że jeśli zacząłeś od niechlujnego kodu z węzłem zależności, to właśnie to (naprawdę) masz, po prostu odwrócone. Właśnie tam pojawia się problem, że każda zmiana implementacji musi również zmienić jej interfejs.
Punktem inwersji zależności jest selektywne odwracanie zależności, które powodują splątanie rzeczy. Ci, którzy powinni przejść z A do B do C, nadal to robią, to ci, którzy przechodzili z C do A, teraz przechodzą z A do C.
Wynikiem powinien być wykres zależności od cykli - DAG. Istnieją różne narzędzia , które sprawdzą tę właściwość i narysują wykres.
Aby uzyskać pełniejsze wyjaśnienie, zobacz ten artykuł :
źródło