Dlaczego podklasowanie jest zbyt złe (a zatem dlaczego powinniśmy używać prototypów, aby to zlikwidować)?

Odpowiedzi:

19

Dlaczego podklasowanie jest zbyt złe?

„Zbyt dużo” to wezwanie do sądu; ale weź to ode mnie, to jest złe. Kod, z którym pracuję, ma dziedziczenie DEEP, a kod jest cholernie trudny do odczytania, zrozumienia, śledzenia, śledzenia, debugowania itp. Itd. Pisanie kodu testowego dla tych rzeczy jest praktycznie niemożliwe.

Wzór prototypowy eliminuje podklasowanie

Czy pytanie to oznacza „eliminuje zbyt wiele podklas?”. Ten wzorzec wymaga klonowania obiektu „szablonu”, aby uniknąć podklasowania, przynajmniej w tym momencie. Nie ma reguły, która mówi, że „szablon” nie może być podklasą.

Preferuj kompozycję zamiast dziedziczenia

Pomysł ten obejmuje również delegowanie i agregację. Zgodnie z tą heurystyką oznacza to, że twoje oprogramowanie jest bardziej elastyczne, łatwiejsze w utrzymaniu, rozszerzaniu i ponownym użyciu.

Gdy klasa składa się z części , można je zastąpić w czasie wykonywania . Ma to ogromny wpływ na testowalność.

Testowanie jest łatwiejsze . Możesz użyć fałszywych części (tj. „Kpiny”, „dublowanie” i inne rozmowy testowe). Głębokie dziedziczenie naszego kodu oznacza, że ​​musimy utworzyć instancję całej hierarchii, aby ją przetestować. W naszym przypadku nie jest to możliwe bez uruchomienia kodu w prawdziwym środowisku. Na przykład potrzebujemy bazy danych w celu tworzenia instancji obiektów biznesowych.

Zmiany wiążą się z efektami ubocznymi i niepewnością - im bardziej „bazowa” klasa, tym bardziej rozpowszechnione efekty, na dobre i na złe. Mogą wystąpić pożądane zmiany, których nie odważysz się wprowadzić z powodu niepewności skutków ubocznych. Lub zmiana, która jest korzystna dla jednego miejsca w naszym łańcuchu spadkowym, jest niekorzystna dla innego. To z pewnością moje doświadczenie.

radarbob
źródło
Byłbym bardzo zainteresowany, aby zobaczyć kilka przykładów, kiedy dziedziczenie utrudnia testowanie (szczególnie kpiny) i jak pomaga w tym kompozycja.
Armand
@alison: metoda w klasie pochodnej, która wykorzystuje inną metodę z klasy podstawowej, w której implementacja w klasie podstawowej praktycznie uniemożliwia testowanie scenariuszy błędów. Gdyby taka była kompozycja, łatwiej byłoby wstrzyknąć obiekt, który powoduje błąd
Rune FS
@allison: Przykłady? Gdy podstawa kodu jest bardzo duża, gdy klasy podstawowe są używane przez dziesiątki klas bezpośrednio, a setki przez dziedziczenie. Ponieważ punktem głębokiego dziedziczenia jest ponowne użycie, a więc klasa podstawowa jest uogólniona do tego stopnia, że ​​ślad stosu debuggera nie może pokazać, jakie metody są faktycznie wywoływane. Wreszcie, gdy nie ma już zastrzyku zależności wbudowanego w taki Frankenstein. Klasa najwyższego poziomu może być „pół tuzinem” lub więcej innymi rzeczami, które łącznie „mają” dziesiątki obiektów wewnętrznych - każda z własnym domem rozrywki.
radarbob,
8

Myślę, że jednym z powodów, dla których uznano to za złą praktykę, jest to, że z biegiem czasu każda podklasa może zawierać atrybuty i metody, które nie mają sensu dla tego obiektu, im dalej schodzisz w dół łańcucha (w słabo rozwiniętej hierarchii). Możesz skończyć z rozdętą klasą, która zawiera przedmioty, które nie mają sensu dla tej klasy.

Jestem do tego agnostyczny. Dogmatyczne wydaje się stwierdzenie, że jest złe. Wszystko jest złe, gdy jest niewłaściwie używane.

jmq
źródło
2

Dwa powody.

  1. To wydajność środowiska wykonawczego. Za każdym razem, gdy wywołujesz podklasę z nadklasy, wykonujesz wywołanie metody, więc jeśli zagnieździłeś pięć klas i wywołujesz metodę w klasie podstawowej, będziesz wykonywać pięć wywołań metod. Większość kompilatorów / środowisk wykonawczych spróbuje to rozpoznać i zoptymalizować dodatkowe połączenia, ale jest to bezpieczne tylko w bardzo prostych przypadkach.

  2. To zdrowy rozsądek waszych kolegów programistów. Jeśli zagnieżdżycie pięć klas, muszę zbadać wszystkie cztery klasy nadrzędne, aby ustalić, czy naprawdę wywołujecie metodę w klasie podstawowej, staje się to żmudne. Podczas debugowania programu może być również konieczne wykonanie pięciu wywołań metod.

James Anderson
źródło
6
-1: Masz rację co do # 2; ale nie o nr 1. Kilka warstw dziedziczenia niekoniecznie wpływa na wydajność środowiska wykonawczego.
Jim G.
Martwisz się o kilka dodatkowych wywołań procedur na poziomie kodu maszynowego, używając OP i dziedziczenia, i martwisz się o zdrowie psychiczne innych programistów .....
Mattnz
@JimG. Może nie, ale może. :) Możliwe jest również użycie pamięci, o które należy się martwić: jeśli nie użyjesz ponownie wszystkich zmiennych bazy, mogą one zostać przydzielone w ramach konstruowania obiektu.
Adam Lear
2

„Zbyt dużo” to wezwanie do osądu.

Podobnie jak w przypadku większości rzeczy w programowaniu, podklasowanie nie jest z natury złe, gdy ma sens. Gdy model rozwiązania problemu ma bardziej sens jako hierarchia niż kompozycja części, podklasowanie może być preferowaną opcją.

To powiedziawszy, preferowanie kompozycji zamiast dziedziczenia jest dobrą regułą.

Gdy podklasujesz , testowanie może być trudniejsze (jak zauważył radarbob ). W głębokiej hierarchii izolowanie problematycznego kodu jest trudniejsze, ponieważ utworzenie instancji podklasy wymaga wprowadzenia całej hierarchii nadklasy i szeregu dodatkowego kodu. Dzięki podejściu kompozycyjnemu możesz izolować i testować każdy komponent obiektu agregującego za pomocą testów jednostkowych, obiektów próbnych itp.

Podklasowanie może również spowodować ujawnienie szczegółów implementacji, których możesz nie chcieć. Utrudnia to kontrolę dostępu do podstawowej reprezentacji danych i wiąże cię z konkretną implementacją modelu tworzenia kopii zapasowych.

Jednym z przykładów, o którym mogę pomyśleć, jest java.util.Properties, który dziedziczy z Hashtable. Klasa Properties przeznaczona jest wyłącznie do przechowywania par String-String (jest to odnotowane w Javadocs). Z powodu dziedziczenia metody put i putAll z Hashtable zostały udostępnione użytkownikom właściwości. Chociaż „właściwy” sposób przechowywania właściwości to setProperty (), absolutnie nic nie stoi na przeszkodzie, aby użytkownik wywołał metodę put () i przekazał ciąg znaków i obiekt.

Zasadniczo semantyka właściwości jest źle zdefiniowana z powodu dziedziczenia. Ponadto, jeśli ktokolwiek zechce kiedykolwiek zmienić obiekt kopii zapasowej właściwości, wpływ będzie miał znacznie większy wpływ na kod korzystający z właściwości, niż gdyby właściwości przyjęły bardziej kompozycyjne podejście.

seggy
źródło
2

Dziedziczenie może w niektórych przypadkach przerwać enkapsulację , a jeśli tak się stanie, jest to złe. Czytaj więcej.


W dawnych dobrych czasach bez dziedziczenia biblioteka publikowałaby interfejs API, a aplikacja korzystająca z niego korzysta z tego, co jest publicznie widoczne, a biblioteka ma teraz swój własny sposób obsługi wewnętrznych narzędzi, które ciągle się zmieniają bez przerywania aplikacji.

W miarę ewolucji potrzeb biblioteka może ewoluować i może mieć więcej klientów; lub może zapewnić rozszerzoną funkcjonalność, dziedzicząc po swojej klasie z innymi smakami. Na razie w porządku.

Jednak podstawową rzeczą jest to, że jeśli aplikacja podejmie klasę i zacznie ją podklasować, teraz podklasa jest właściwie klientem, a nie partnerem wewnętrznym. Jednak w przeciwieństwie do wyraźnego, jednoznacznego sposobu definiowania publicznego API, podklasa jest teraz znacznie głębsza w bibliotece (dostęp do wszystkich prywatnych zmiennych i tak dalej). Nie jest jeszcze źle, ale paskudna postać może zmostkować interfejs API, który jest zbyt duży w aplikacji, a także w bibliotece

W istocie, chociaż nie zawsze tak może być, ale:

Łatwo jest użyć dziedziczenia do przerwania enkapsulacji

Zezwolenie na podklasę może być błędne, jeśli w tym momencie.

Dipan Mehta
źródło
1

Krótka szybka odpowiedź:

To zależy od tego, co robisz.

Rozszerzona nudna odpowiedź:

Deweloper może stworzyć aplikację. przy użyciu szablonów podklas lub prototypowych. Czasami jedna technika działa lepiej. Czasami inna technika działa lepiej.

Wybór za pomocą techniki działa najlepiej, wymaga również rozważenia, czy obecny jest scenariusz „Wielokrotnego dziedziczenia”. Nawet jeśli język programowania obsługuje tylko pojedyncze dziedziczenie, szablony lub prototypy.

Uwaga uzupełniająca:

Niektóre odpowiedzi dotyczą „wielokrotnego dziedziczenia”. Myślenie, a nie główne pytanie, jest związane z tematem. Istnieje kilka podobnych postów na temat „wielokrotnego dziedziczenia a składu”. Miałem przypadek, w którym mieszałem oba.

umlcat
źródło