Dziedziczenie - wykładniczy rozrost klas

0

Hej,
Mam daną klasę A, która zawiera puste metody (ma definicje ale są puste):

claas A
{
public:
  virtual void test1() {}
  virtual void test2() {}
};

Oraz klasę B, która dziedziczy po A i nadpisuje metodę test1:

class B : public A
{
public:
  void test1() override {...}
};

I teraz mam kolejną klasę, która napisuję metodę test2. Klasy C i B nie wykluczają się. Taka klasa C powinna rozszerzyć zatem klasę A ale i B. Mamy już 4 kombinacje. Dojdzie kolejną klasa, będzie 9 kombinacji.
Liczba plików rośnie wykładniczo.

Jakiś pomysł jak to zrobić dynamicznie? Myślałem nad szablonem ale definicja klas koniecznie musi znajdować się w odosobnionym pliku .cpp oraz muszę mieć decyzje, które klasy mogą rozszerzać podrzędne.

5

Taka klasa C powinna rozszerzyć zatem klasę A ale i B

? Po co Ci dziedziczenie z B?

To mi wyglada jakbys naduzywal dziedziczenia. Na pewno nie chcesz / nie mozesz uzyc tutaj kompozycji?

5

Dobra z a teraz zamiast A, B i C napisz konkretnie jakiś życiowy przykład. Poza tym twoja klasa A wygląda na złamanie zasady separacji interfejsów i zasady podstawiania, skoro robisz klasę B która dziedziczy z A i nie nadpisuje wszystkich metod czysto wirtualnych. To ci kiedyś wybuchnie w twarz, bo ktoś zawoła sobie tą metodę.
Szklana kula mówi że powinienes mieć 2 osobne interfejsy, jeden z metodą test1 a drugi z test2.

1

Btw, zamiast pustych metod zrób je czysto wirtualne.

1
Focusx napisał(a):

I teraz mam kolejną klasę, która napisuję metodę test2. Klasy C i B nie wykluczają się. Taka klasa C powinna rozszerzyć zatem klasę A ale i B. Mamy już 4 kombinacje.
Dojdzie kolejną klasa, będzie 9 kombinacji.

Jakie kombinacje?

1
Focusx napisał(a):

Liczba plików rośnie wykładniczo.

W jaki sposób?

1

Z tego co napisałeś wynika, że to powinny być osobne interfejsy jak napisał Shalom. Jeżeli z jakiegoś powodu, którego nam nie podałeś, to nie mogą być osobne interfejsy, to robisz kompozycje jak napisał fasadin.
Ewentualnie mixiny, ale to zależy co dokładnie chcesz osiągnąć.
Dziedziczenie diamentowe, które próbujesz zrobić nie jest zbyt często spotykane w praktyce.

PS. Wygląda to na złe zrozumienie programowania obiektowego i nadużywania dziedziczenia.

0

Przykład z życia (związany z OpenGL).
Klasa bazowa to obiekt 3d. Taka klasa zawiera macierz transformacji, mesh oraz shadery. Klasa dziedzicząca po niej to object3dInstanced - jedna metoda renderująca kilka obiektów na raz, z tym samym meshem (nieco zmienia jej definicje), nieco inne shadery.

Klasa bazowa (Object3d) zawiera również pustą definicje metody "void update()", która jest zawsze wywoływana w metodzie render().
Tu pojawia się klasa C, czyli SkinnedObject3d. Taka klasa może dziedziczyć po Object3d lub po object3dInstanced. Nadpisuje ona bowiem wyłącznie metodę update(). Shadery chcę zrobić by generowały się dynamicznie w zależności jaką mamy klasę nadrzędną.

Po co tak? Czemu nie używam zwykłego if'a?
A no dlatego, że do animacji obiektu pojawi się wiele nowych pól, które są zbędne dla obiektu statycznego (bez animacji szkieletowej).

Planem awaryjnym jest utworzenie wektora w klasie Object3d, który będzie zawierał dedykowane dla niej komponenty. Jak dla mnie to mniej przejrzyste będzie.

1

Mi to wygląda na książkowy przykład na wzorzec Composite.

0

W zasadzie może faktycznie Kompozyt będzie najlepszy.
Dzięki za podpowiedzi.

1

Pomyśl też może o tym, że klasa zwykle sugeruje że będziesz miał wiele takich obiektów, a u ciebie nie jestem pewien czy to jest prawda. Tworzenie nowej klasy tylko po to żeby nadpisać metodę bo potrzebujesz jej w jednym obiekcie, to błąd. W takiej sytuacji można pomyśleć o jakimś TemplateMethod albo o kompozycji, gdzie jednym z parametrów twojej klasy jest funkcja albo funktor.

Załóżmy że chciałbym mieć dwa obiekty klasy FooBar, jeden w metodzie test ma robić X, a drugi ma robić Y. Poza tym obiekty są identyczne (z punktu widzenia strukturalnego). Zamiast robić tutaj dwie dziedziczące z FooBar klasy, mogę po prostu w konstruktorze przekazać lambdę albo obiekt klasy Function który ma jedną funkcję, tą która mnie interesuje. A sam FooBar będzie w test odpalać function.run().

1
nalik napisał(a):

PS. Wygląda to na złe zrozumienie programowania obiektowego i nadużywania dziedziczenia.

Jak ja sobie określam, "pierwsza fala OOP" była zdominowana przez dziedziczenie *) - to ta sama gdzie rzeczownik to obiekt, a czasownik to metoda, myślenie przez interfejs niemal nie istniejące.
Potem (czasy wzorców itd) czasowniki "można było" już odwzorować w klasach, dziedziczenia znacznie mniej itd... ja to sam dla siebie nazywam "drugą falą".

W starych tutorach, książkach, na konserwatywnych uczelniach jeszcze trwa "pierwsza fala"

*) Jak ktoś zna(ł) frameworki graficzne Turbo Vision, OWL, MFC to tam WSZYSTKO dziedziczy z pnia a ilość pięter dochodzi do kilkunastu

1

*) Jak ktoś zna(ł) frameworki graficzne Turbo Vision, OWL, MFC to tam WSZYSTKO dziedziczy z pnia a ilość pięter dochodzi do kilkunastu

Chyba w każdym UI toolkicie jest głęboka hierarchia. Przykłady w (chyba) WPF:

  • System.Windows.Controls.Ribbon / RibbonButton: Object -> DispatcherObject -> DependencyObject -> Visual -> UIElement -> FrameworkElement -> Control -> ContentControl -> ButtonBase -> Button -> RibbonButton
  • System.Windows.Controls / DataGrid: Object -> DispatcherObject -> DependencyObject -> Visual -> UIElement -> FrameworkElement -> Control -> ItemsControl -> Selector -> MultiSelector -> DataGrid

Prawdopodobnie takie głębokie hierarchie dziedziczenia w UI toolkitach są inspiracją do powielania ich w modelach biznesowych.

1 użytkowników online, w tym zalogowanych: 0, gości: 1