Dlaczego powinienem unikać wielokrotnego dziedziczenia w C ++?

167

Czy dobrym pomysłem jest korzystanie z dziedziczenia wielokrotnego, czy mogę zamiast tego robić inne rzeczy?

Hai
źródło

Odpowiedzi:

259

Dziedziczenie wielokrotne (w skrócie MI) pachnie , co oznacza, że zwykle odbywało się to ze złych powodów i powróci w twarz opiekuna.

Podsumowanie

  1. Zastanów się nad kompozycją funkcji zamiast dziedziczenia
  2. Uważaj na Diament Strachu
  3. Rozważ dziedziczenie wielu interfejsów zamiast obiektów
  4. Czasami wielokrotne dziedziczenie jest właściwą rzeczą. Jeśli tak, użyj go.
  5. Przygotuj się do obrony swojej architektury dziedziczonej wielokrotnie podczas przeglądów kodu

1. Może kompozycja?

Dotyczy to dziedziczenia, a więc jeszcze bardziej dotyczy dziedziczenia wielokrotnego.

Czy Twój obiekt naprawdę musi dziedziczyć po innym? A Carnie musi dziedziczyć z Enginedo pracy ani z Wheel. A Carma Enginei cztery Wheel.

Jeśli używasz dziedziczenia wielokrotnego do rozwiązania tych problemów zamiast kompozycji, oznacza to, że zrobiłeś coś złego.

2. Diament Strachu

Zazwyczaj masz klasę A, potem Bi Czarówno dziedziczyć po A. I (nie pytaj mnie dlaczego) ktoś zdecyduje, że Dmusi dziedziczyć zarówno po, jak Bi C.

Spotkałem się z tego rodzaju problemem dwa razy w ciągu 8 ośmiu lat i jest to zabawne z powodu:

  1. Ile to był błąd od samego początku (w obu przypadkach Dnie powinien był odziedziczyć po obu Bi C), bo to była zła architektura (właściwie Cnie powinna w ogóle istnieć ...)
  2. Ile opiekunów za to płaciło, ponieważ w C ++ klasa nadrzędna Abyła obecna dwukrotnie w swojej klasie wnuków D, a zatem aktualizacja jednego pola nadrzędnego A::fieldoznaczała albo aktualizację go dwukrotnie (przez B::fieldi C::field), albo po cichu coś poszło nie tak i awarię później (nowy wskaźnik w B::fieldi usuń C::field...)

Użycie słowa kluczowego virtual w C ++ do zakwalifikowania dziedziczenia pozwala uniknąć podwójnego układu opisanego powyżej, jeśli nie tego chcesz, ale w każdym razie, z mojego doświadczenia, prawdopodobnie robisz coś źle ...

W hierarchii obiektów powinieneś starać się zachować hierarchię jako drzewo (węzeł ma JEDNEGO rodzica), a nie jako wykres.

Więcej o Diamencie (edytuj 2017-05-03)

Prawdziwy problem z Diamentem Strachu w C ++ ( zakładając, że projekt jest dobry - poproś o sprawdzenie kodu! ), Polega na tym, że musisz dokonać wyboru :

  • Czy pożądane jest, aby klasa Awystępowała dwukrotnie w Twoim układzie i co to oznacza? Jeśli tak, to z całą pewnością odziedzicz po nim dwukrotnie.
  • jeśli ma istnieć tylko raz, to odziedziczy po niej wirtualnie.

Ten wybór jest nieodłącznym elementem problemu, aw C ++, w przeciwieństwie do innych języków, można to zrobić bez dogmatów narzucających projekt na poziomie języka.

Ale jak wszystkie moce, z tą mocą wiąże się odpowiedzialność: poproś o sprawdzenie swojego projektu.

3. Interfejsy

Wielokrotne dziedziczenie zera lub jednej konkretnej klasy i zera lub większej liczby interfejsów jest zwykle OK, ponieważ nie napotkasz Diamentu Strachu opisanego powyżej. W rzeczywistości tak się robi w Javie.

Zwykle to, co masz na myśli, gdy C dziedziczy po Ai Bto, że użytkownicy mogą używać Ctak, jakby to był Ai / lub tak, jakby był B.

W C ++ interfejs jest klasą abstrakcyjną, która ma:

  1. cała jej metoda zadeklarowana jako czysta wirtualna (z przyrostkiem = 0) (usunięto 2017-05-03)
  2. brak zmiennych składowych

Dziedziczenie wielokrotne od zera do jednego rzeczywistego obiektu i zera lub większej liczby interfejsów nie jest uważane za „śmierdzące” (a przynajmniej nie tak bardzo).

Więcej informacji o abstrakcyjnym interfejsie C ++ (edytuj 2017-05-03)

Po pierwsze, wzorzec NVI może być użyty do stworzenia interfejsu, ponieważ rzeczywiste kryteria to brak stanu (tj. Brak zmiennych składowych , z wyjątkiem this). Celem twojego abstrakcyjnego interfejsu jest opublikowanie umowy („możesz nazywać mnie tak i tak”), nic więcej, nic mniej. Ograniczeniem posiadania tylko abstrakcyjnej metody wirtualnej powinien być wybór projektu, a nie obowiązek.

Po drugie, w C ++ sensowne jest dziedziczenie wirtualne z abstrakcyjnych interfejsów (nawet z dodatkowymi kosztami / pośrednimi). Jeśli tego nie zrobisz, a dziedziczenie interfejsu pojawia się wielokrotnie w Twojej hierarchii, będziesz mieć niejasności.

Po trzecie, orientacja obiektu jest super, ale nie jest to jedyna prawda Out There TM w C ++. Używaj właściwych narzędzi i zawsze pamiętaj, że w C ++ masz inne paradygmaty, które oferują różnego rodzaju rozwiązania.

4. Czy naprawdę potrzebujesz wielokrotnego dziedziczenia?

Czasem tak.

Zazwyczaj twoja Cklasa dziedziczy z Ai Bi Ai Bsą dwóch niepowiązanych obiektów (czyli nie w tej samej hierarchii, nic wspólnego, różnych koncepcji, etc.).

Na przykład, możesz mieć system o Nodeswspółrzędnych X, Y, Z, zdolny do wykonywania wielu obliczeń geometrycznych (być może punkt, część obiektów geometrycznych), a każdy Węzeł jest Automatycznym Agentem, zdolnym do komunikowania się z innymi agentami.

Być może masz już dostęp do dwóch bibliotek, z których każda ma własną przestrzeń nazw (kolejny powód, aby używać przestrzeni nazw ... Ale używasz przestrzeni nazw, prawda?), Jedna istota, geoa drugaai

Więc masz własne own::Nodepochodzenie zarówno z, jak ai::Agenti geo::Point.

To jest moment, w którym powinieneś zadać sobie pytanie, czy zamiast tego nie powinieneś używać kompozycji. Jeśli own::Nodenaprawdę jest zarówno a, jak ai::Agenti a geo::Point, to kompozycja nie wystarczy.

Wtedy będziesz potrzebować wielokrotnego dziedziczenia, aby own::Nodekomunikować się z innymi agentami zgodnie z ich pozycją w przestrzeni 3D.

(Zauważysz, że ai::Agenti geo::Pointjesteś całkowicie, całkowicie, całkowicie NIEZWIĄZANY ... To drastycznie zmniejsza niebezpieczeństwo wielokrotnego dziedziczenia)

Inne sprawy (edytuj 2017-05-03)

Istnieją inne przypadki:

  • przy użyciu (miejmy nadzieję prywatnego) dziedziczenia jako szczegółów implementacji
  • niektóre idiomy C ++, takie jak zasady, mogą używać wielokrotnego dziedziczenia (gdy każda część musi komunikować się z innymi za pośrednictwem this)
  • wirtualne dziedziczenie ze std :: wyjątek ( czy wirtualne dziedziczenie jest konieczne dla wyjątków? )
  • itp.

Czasami możesz użyć kompozycji, a czasami MI jest lepsze. Chodzi o to, że masz wybór. Zrób to odpowiedzialnie (i poproś o sprawdzenie kodu).

5. Czy powinienem zatem dziedziczyć wielokrotne?

Z mojego doświadczenia wynika, że ​​przez większość czasu nie. MI nie jest odpowiednim narzędziem, nawet jeśli wydaje się działać, ponieważ może być używane przez leniwych do łączenia elementów bez zdawania sobie sprawy z konsekwencji (takich jak tworzenie Carzarówno an, jak Enginei a Wheel).

Ale czasami tak. A wtedy nic nie będzie działać lepiej niż MI.

Ale ponieważ MI jest śmierdzący, przygotuj się na obronę swojej architektury w przeglądach kodu (a obrona jej jest dobra, ponieważ jeśli nie jesteś w stanie jej obronić, nie powinieneś tego robić).

paercebal
źródło
4
Twierdziłbym, że nie jest to nigdy konieczne i tak rzadko przydatne, że nawet jeśli jest idealnie dopasowane, oszczędności wynikające z delegowania nie zrekompensują zamieszania programistom, którzy nie są przyzwyczajeni do jego używania, ale poza tym dobre podsumowanie.
Bill K
2
Dodałbym do punktu 5, że kod, który używa MI powinien mieć komentarz wyjaśniający powód tuż obok, więc nie musisz wyjaśniać tego ustnie w przeglądzie kodu. Bez tego ktoś, kto nie jest twoim recenzentem, może zakwestionować twoją dobrą ocenę, gdy zobaczy twój kod i możesz nie mieć możliwości jego obrony.
Tim Abell
ponieważ nie napotkasz Diamentu Strachu opisanego powyżej. ” Dlaczego nie?
curiousguy
1
Używam MI jako wzorca obserwatora.
Calmarius
13
@BillK: Oczywiście, że nie jest to konieczne. Jeśli masz kompletny język Turning bez MI, możesz zrobić wszystko, co potrafi język z MI. Więc tak, nie jest to konieczne. Zawsze. To powiedziawszy, może to być bardzo przydatne narzędzie, a tak zwany Dreaded Diamond… Nigdy tak naprawdę nie rozumiałem, dlaczego w ogóle istnieje słowo „straszny”. Właściwie jest to dość łatwe do zrozumienia i zwykle nie stanowi problemu.
Thomas Eding,
145

Z wywiadu z Bjarne Stroustrupem :

Ludzie całkiem słusznie twierdzą, że nie potrzebujesz dziedziczenia wielokrotnego, ponieważ wszystko, co możesz zrobić z dziedziczeniem wielokrotnym, możesz również zrobić z dziedziczeniem pojedynczym. Po prostu użyj sztuczki delegowania, o której wspomniałem. Co więcej, w ogóle nie potrzebujesz dziedziczenia, ponieważ wszystko, co robisz z dziedziczeniem pojedynczym, możesz również zrobić bez dziedziczenia, przesyłając dalej przez klasę. Właściwie nie potrzebujesz też żadnych klas, ponieważ możesz to wszystko zrobić za pomocą wskaźników i struktur danych. Ale dlaczego chcesz to zrobić? Kiedy wygodnie jest korzystać z udogodnień językowych? Kiedy wolisz obejście? Widziałem przypadki, w których wielokrotne dziedziczenie jest przydatne, a nawet widziałem przypadki, w których dość skomplikowane wielokrotne dziedziczenie jest przydatne. Generalnie wolę korzystać z udogodnień oferowanych przez język, aby obejść ten problem

Nemanja Trifunovic
źródło
6
Jest kilka funkcji C ++, których brakowało mi w C # i Javie, chociaż posiadanie ich utrudniłoby projektowanie. Na przykład gwarancje niezmienności const- musiałem pisać niezgrabne obejścia (zwykle przy użyciu interfejsów i kompozycji), gdy klasa naprawdę potrzebowała mutowalnych i niezmiennych wariantów. Jednakże, nigdy nie raz brakowało wielokrotne dziedziczenie, i nigdy nie czułem, że muszę napisać obejścia ze względu na brak tej funkcji. To jest różnica. W każdym przypadku, jaki kiedykolwiek widziałem, nie używanie MI jest lepszym wyborem projektowym, a nie obejściem.
BlueRaja - Danny Pflughoeft
24
+1: Doskonała odpowiedź: jeśli nie chcesz jej używać, nie używaj jej.
Thomas Eding,
38

Nie ma powodu, aby tego unikać i może być bardzo przydatne w sytuacjach. Musisz jednak zdawać sobie sprawę z potencjalnych problemów.

Największy z nich to diament śmierci:

class GrandParent;
class Parent1 : public GrandParent;
class Parent2 : public GrandParent;
class Child : public Parent1, public Parent2;

Masz teraz dwie „kopie” GrandParent in Child.

C ++ pomyślał o tym jednak i pozwala na wirtualne dziedziczenie w celu obejścia problemów.

class GrandParent;
class Parent1 : public virtual GrandParent;
class Parent2 : public virtual GrandParent;
class Child : public Parent1, public Parent2;

Zawsze sprawdzaj swój projekt, upewnij się, że nie używasz dziedziczenia, aby zaoszczędzić na ponownym wykorzystaniu danych. Jeśli możesz przedstawić to samo z kompozycją (a zazwyczaj możesz), jest to znacznie lepsze podejście.

billybob
źródło
21
A jeśli zablokujesz dziedziczenie i zamiast tego użyjesz tylko kompozycji, zawsze masz dwa GrandParentw Child. Istnieje strach przed MI, ponieważ ludzie po prostu myślą, że mogą nie rozumieć zasad językowych. Ale każdy, kto nie może zrozumieć tych prostych zasad, nie może również napisać nietrywialnego programu.
curiousguy
1
Brzmi to szorstko, wszystkie zasady C ++ są bardzo rozbudowane. OK, więc nawet jeśli poszczególne zasady są proste, jest ich wiele, co sprawia, że ​​całość nie jest prosta, przynajmniej dla człowieka.
Danio pręgowany
11

Zobacz w: wielokrotne dziedziczenie .

Dziedziczenie wielokrotne spotkało się z krytyką i jako takie nie jest wdrażane w wielu językach. Krytyka obejmuje:

  • Zwiększona złożoność
  • Niejednoznaczność semantyczna jest często podsumowywana jako problem diamentów .
  • Brak możliwości jawnego wielokrotnego dziedziczenia z jednej klasy
  • Kolejność dziedziczenia zmieniająca semantykę klas.

Wielokrotne dziedziczenie w językach z konstruktorami w stylu C ++ / Java zaostrza problem dziedziczenia konstruktorów i łańcuchów konstruktorów, tworząc w ten sposób problemy z utrzymaniem i rozszerzalnością w tych językach. Obiekty w relacjach dziedziczenia o bardzo różnych metodach konstrukcji są trudne do zaimplementowania w paradygmacie tworzenia łańcuchów konstruktorów.

Nowoczesny sposób rozwiązania tego problemu polega na użyciu interfejsu (klasa czysto abstrakcyjna), takiego jak interfejs COM i Java.

Mogę zamiast tego zrobić inne rzeczy?

Tak, możesz. Mam zamiar okraść GoF .

  • Zaprogramuj interfejs, a nie implementację
  • Przedkładaj kompozycję nad dziedziczenie
Eugene Yokota
źródło
8

Dziedziczenie publiczne jest relacją IS-A i czasami klasa będzie rodzajem kilku różnych klas i czasami ważne jest, aby to odzwierciedlić.

Czasami przydatne są także „miksy”. Na ogół są to małe klasy, zwykle nie dziedziczące po niczym, zapewniające użyteczną funkcjonalność.

Tak długo, jak hierarchia dziedziczenia jest dość płytka (jak prawie zawsze powinna być) i dobrze zarządzana, jest mało prawdopodobne, że otrzymasz przerażające dziedzictwo diamentowe. Diament nie jest problemem we wszystkich językach, które używają wielokrotnego dziedziczenia, ale sposób jego traktowania w C ++ jest często niezręczny i czasami zagadkowy.

Chociaż spotkałem się z przypadkami, w których wielokrotne dziedziczenie jest bardzo przydatne, w rzeczywistości są one dość rzadkie. Jest to prawdopodobne, ponieważ wolę używać innych metod projektowania, gdy tak naprawdę nie potrzebuję dziedziczenia wielokrotnego. Wolę unikać mylących konstrukcji językowych i łatwo jest tworzyć przypadki dziedziczenia, w których trzeba naprawdę dobrze przeczytać podręcznik, aby dowiedzieć się, co się dzieje.

David Thornley
źródło
6

Nie należy „unikać” wielokrotnego dziedziczenia, ale należy być świadomym problemów, które mogą się pojawić, takich jak „problem z diamentami” ( http://en.wikipedia.org/wiki/Diamond_problem ) i ostrożnie traktować przekazaną władzę , tak jak powinieneś ze wszystkimi mocami.

jwpfox
źródło
1
+1: Tak jak nie powinieneś unikać wskaźników; po prostu musisz być świadomy ich konsekwencji. Ludzie muszą przestać wyrzucać zwroty (takie jak „MI jest złe”, „nie optymalizuj”, „goto jest złe”), których nauczyli się z internetu tylko dlatego, że jakiś hipis powiedział, że to jest złe dla ich higieny. Najgorsze jest to, że nigdy nawet nie próbowali używać takich rzeczy i po prostu przyjmują to tak, jakby mówiło się z Biblii.
Thomas Eding,
1
Zgadzam się. Nie „unikaj” tego, ale poznaj najlepszy sposób rozwiązania problemu. Może wolałbym użyć cech kompozycyjnych, ale C ++ tego nie ma. Może wolałbym użyć automatycznej delegacji „uchwytów”, ale C ++ tego nie ma. Dlatego używam ogólnego i tępego narzędzia MI do kilku różnych celów, być może jednocześnie.
JDługosz
3

Ryzykując nieco abstrakcyjności, myślę o dziedziczeniu w ramach teorii kategorii wydaje mi się pouczające.

Jeśli pomyślimy o wszystkich naszych klasach i strzałkach między nimi oznaczających relacje dziedziczenia, to coś takiego

A --> B

oznacza, że class Bpochodzi z class A. Zauważ, że podane

A --> B, B --> C

mówimy, że C pochodzi od B, które pochodzi od A, więc mówi się również, że C pochodzi od A, a zatem

A --> C

Ponadto mówimy, że dla każdej klasy, z Aktórej Awywodzi się trywialnie A, nasz model dziedziczenia spełnia definicję kategorii. W bardziej tradycyjnym języku mamy kategorię Classzawierającą obiekty, wszystkie klasy i morfizmy, relacje dziedziczenia.

To trochę konfiguracji, ale spójrzmy na nasz Diament Zagłady:

C --> D
^     ^
|     |
A --> B

To podejrzany schemat, ale wystarczy. Więc Ddziedziczy wszystko A, Bi C. Co więcej, i coraz bliżej odpowiedzi na pytanie OP, Ddziedziczy również z dowolnej nadklasy A. Możemy narysować diagram

C --> D --> R
^     ^
|     |
A --> B
^ 
|
Q

Teraz problemy związane z Diamonda Śmierci oto kiedy Ci Bpodzielić nieruchomość / nazwy metod i robi się niejednoznaczne; jeśli jednak przeniesiemy do tego jakieś wspólne zachowanie, Aniejednoznaczność znika.

Ujmując kategorycznie, chcemy A, Bi Cbyć takim, że jeśli Bi Cdziedziczenie z Qtego czasu Amoże być przepisane jako podklasa Q. To powoduje Acoś, co nazywa się wypychaniem .

Istnieje również symetryczna konstrukcja Dzwana pullback . Jest to w zasadzie najbardziej użyteczna klasa, jaką można skonstruować, która dziedziczy po obu Bi C. Oznacza to, że jeśli masz jakąkolwiek inną klasę, Rdziedzicząc mnożenie po Bi C, to Djest to klasa, w której Rmożna ją przepisać jako podklasę D.

Upewnienie się, że twoje wierzchołki diamentu są wycofane i wypchnięte, daje nam dobry sposób na ogólne radzenie sobie z konfliktami nazw lub problemami z konserwacją, które mogą wystąpić w przeciwnym razie.

Zauważ , że odpowiedź Paercebala zainspirowała to, ponieważ jego napomnienia wynikają z powyższego modelu, biorąc pod uwagę, że pracujemy w pełnej kategorii Klasa wszystkich możliwych klas.

Chciałem uogólnić jego argument na coś, co pokazuje, jak skomplikowane relacje wielokrotnego dziedziczenia mogą być zarówno potężne, jak i bezproblemowe.

TL; DR Pomyśl o relacjach dziedziczenia w swoim programie jako tworzących kategorię. Następnie możesz uniknąć problemów z Diamentem Zagłady, wykonując wypychania klas dziedziczonych wielokrotnie i symetrycznie, tworząc wspólną klasę nadrzędną, która jest wycofaniem.

Ncat
źródło
3

Używamy Eiffla. Mamy doskonałe MI. Bez obaw. Nie ma problemów. Łatwe zarządzanie. Są chwile, aby NIE używać MI. Jest to jednak przydatne bardziej niż ludzie zdają sobie sprawę, ponieważ: A) posługują się niebezpiecznym językiem, który nie radzi sobie dobrze -LUB- B) są zadowoleni z tego, jak pracowali z MI przez lata -LUB- C) z innych powodów ( zbyt liczne, aby je wymienić, jestem pewien - zobacz odpowiedzi powyżej).

Dla nas używanie Eiffla, MI jest tak samo naturalne jak wszystko inne i jest kolejnym doskonałym narzędziem w zestawie narzędzi. Szczerze mówiąc, nie obchodzi nas, że nikt inny nie używa Eiffla. Bez obaw. Cieszymy się z tego, co mamy i zapraszamy do obejrzenia.

Kiedy patrzysz: zwróć szczególną uwagę na bezpieczeństwo Pustki i wyeliminowanie dereferencji wskaźnika zerowego. Kiedy wszyscy tańczymy dookoła MI, twoje wskazówki gubią się! :-)

Larry
źródło
2

Każdy język programowania ma nieco inne podejście do programowania obiektowego z zaletami i wadami. Wersja C ++ kładzie nacisk bezpośrednio na wydajność i ma towarzyszącą wadę, że pisanie nieprawidłowego kodu jest niepokojąco łatwe - i jest to prawdą w przypadku wielokrotnego dziedziczenia. W konsekwencji istnieje tendencja do odciągania programistów od tej funkcji.

Inne osoby zadały sobie pytanie, do czego dziedziczenie wielokrotne nie jest dobre. Ale widzieliśmy wiele komentarzy, które mniej więcej sugerują, że powodem, dla którego należy tego unikać, jest to, że nie jest to bezpieczne. Cóż, tak i nie.

Jak to często bywa w C ++, jeśli zastosujesz się do podstawowych wskazówek, możesz z nich bezpiecznie korzystać bez ciągłego „patrzenia przez ramię”. Główną ideą jest to, że rozróżnia się specjalny rodzaj definicji klasy zwany „mieszanką”; class jest mieszanką, jeśli wszystkie jej funkcje składowe są wirtualne (lub czysto wirtualne). Wtedy możesz dziedziczyć z jednej głównej klasy i tylu „miksów”, ile chcesz - ale powinieneś dziedziczyć mieszanki ze słowem kluczowym „wirtualny”. na przykład

class CounterMixin {
    int count;
public:
    CounterMixin() : count( 0 ) {}
    virtual ~CounterMixin() {}
    virtual void increment() { count += 1; }
    virtual int getCount() { return count; }
};

class Foo : public Bar, virtual public CounterMixin { ..... };

Moja sugestia jest taka, że ​​jeśli zamierzasz używać klasy jako klasy mieszanej, zastosuj także konwencję nazewnictwa, aby ułatwić każdemu czytającemu kod, aby zobaczyć, co się dzieje i sprawdzić, czy grasz zgodnie z zasadami podstawowych wytycznych . Przekonasz się, że działa to znacznie lepiej, jeśli twoje miksy mają również domyślne konstruktory, tylko ze względu na sposób działania wirtualnych klas podstawowych. I pamiętaj, aby wszystkie destruktory też były wirtualne.

Zwróć uwagę, że moje użycie tutaj słowa „mix-in” nie jest tym samym, co sparametryzowana klasa szablonu (zobacz ten link po dobre wyjaśnienie), ale myślę, że jest to uczciwe użycie terminologii.

Nie chcę teraz sprawiać wrażenia, że ​​jest to jedyny sposób na bezpieczne korzystanie z dziedziczenia wielokrotnego. To tylko jeden sposób, który jest dość łatwy do sprawdzenia.

sfkleach
źródło
2

Powinieneś używać go ostrożnie, są takie przypadki, jak problem diamentów , kiedy sprawy mogą się skomplikować.

tekst alternatywny
(źródło: learncpp.com )

CMS
źródło
12
To zły przykład, ponieważ kopiarka powinna składać się ze skanera i drukarki, a nie dziedziczyć po nich.
Svante
2
W każdym razie a Printernie powinno być nawet PoweredDevice. A Printeroznacza drukowanie, a nie zarządzanie energią. Implementacja określonej drukarki może wymagać zarządzania energią, ale te polecenia zarządzania energią nie powinny być ujawniane bezpośrednio użytkownikom drukarki. Nie mogę sobie wyobrazić żadnego rzeczywistego wykorzystania tej hierarchii.
curiousguy
1

Poza wzorem rombu wielokrotne dziedziczenie sprawia, że ​​model obiektu jest trudniejszy do zrozumienia, co z kolei zwiększa koszty utrzymania.

Kompozycja jest z natury łatwa do zrozumienia, zrozumienia i wyjaśnienia. Pisanie kodu może być uciążliwe, ale dobre środowisko IDE (minęło kilka lat, odkąd pracowałem z Visual Studio, ale z pewnością wszystkie środowiska Java IDE mają świetne narzędzia automatyzujące skróty do kompozycji) powinny pokonać tę przeszkodę.

Jeśli chodzi o utrzymanie, „problem diamentów” pojawia się również w niedosłownych przypadkach dziedziczenia. Na przykład, jeśli masz A i B, a twoja klasa C rozszerza je oba, a A ma metodę `` makeJuice '', która wytwarza sok pomarańczowy i rozszerzasz ją, aby zrobić sok pomarańczowy z odrobiną limonki: co się stanie, gdy projektant B „dodaje metodę„ makeJuice ”, która generuje prąd elektryczny? „A” i „B” może być zgodny „rodzice” już teraz , ale to nie znaczy zawsze będą tak!

Ogólnie rzecz biorąc, maksyma polegająca na unikaniu dziedziczenia, a zwłaszcza dziedziczenia wielokrotnego, jest rozsądna. Jak wszystkie maksymy, są wyjątki, ale musisz upewnić się, że miga zielony neon wskazujący wszystkie wyjątki, które kodujesz (i trenuj swój mózg, aby za każdym razem, gdy zobaczysz takie drzewa dziedziczenia, rysujesz własnym migającym zielonym neonem znak) i od czasu do czasu sprawdzasz, czy wszystko ma sens.

Tom Dibble
źródło
what happens when the designer for 'B' adds a 'makeJuice' method which generates and electrical current?Uhhh, oczywiście pojawia się błąd kompilacji (jeśli użycie jest niejednoznaczne).
Thomas Eding,
1

Kluczową kwestią związaną z MI dla konkretnych obiektów jest to, że rzadko masz obiekt, który zgodnie z prawem powinien „być A i być B”, więc rzadko jest to poprawne rozwiązanie ze względów logicznych. Znacznie częściej masz obiekt C, który jest posłuszny „C może działać jako A lub B”, co można osiągnąć poprzez dziedziczenie i kompozycję interfejsu. Ale nie popełnij błędu - dziedziczenie wielu interfejsów to nadal MI, tylko jej podzbiór.

W szczególności w przypadku języka C ++ kluczową słabością tej funkcji nie jest faktyczne ISTNIENIE wielokrotnego dziedziczenia, ale niektóre konstrukcje, na które zezwala, są prawie zawsze źle sformułowane. Na przykład dziedziczenie wielu kopii tego samego obiektu, na przykład:

class B : public A, public A {};

jest zniekształcony WEDŁUG DEFINICJI. W tłumaczeniu na język angielski jest to „B to A i A”. Tak więc nawet w ludzkim języku istnieje poważna dwuznaczność. Czy chodziło Ci o „B ma 2 As” czy po prostu „B to A” ?. Dopuszczenie takiego patologicznego kodu, a co gorsza uczynienie go przykładem użycia, nie przyniosło C ++ żadnych korzyści, jeśli chodzi o argumentowanie za utrzymaniem tej funkcji w kolejnych językach.

Zack Yezek
źródło
0

Możesz użyć kompozycji zamiast dziedziczenia.

Ogólne wrażenie jest takie, że kompozycja jest lepsza i jest bardzo dobrze omówiona.

Ali Afshar
źródło
4
-1: The general feeling is that composition is better, and it's very well discussed.To nie znaczy, że kompozycja jest lepsza.
Thomas Eding,
Tyle, że tak naprawdę jest lepiej.
Ali Afshar
12
Z wyjątkiem przypadków, w których nie jest lepiej.
Thomas Eding
0

zajmuje 4/8 bajtów na zaangażowaną klasę. (Jeden ten wskaźnik na klasę).

To może nigdy nie być problemem, ale jeśli pewnego dnia będziesz mieć strukturę mikrodanych, która jest instancją miliardów czasu, to będzie.

Ronny Brendel
źródło
1
Pewny tego? Zwykle każde poważne dziedziczenie będzie wymagało wskaźnika w każdym elemencie klasy, ale czy to musi być skalowane wraz z dziedziczonymi klasami? A konkretnie, ile razy miałeś miliardy malutkich struktur danych?
David Thornley
3
@DavidThornley „ ile razy miałeś miliardy malutkich struktur danych? ” Kiedy wymyślasz argumenty przeciwko MI.
curiousguy