Dziedzictwo poszło nie tak

12

Mam kod, w którym spadł dobry model dziedziczenia i próbuję zrozumieć, dlaczego i jak to naprawić. Zasadniczo wyobraź sobie, że masz hierarchię zoo z:

class Animal  
class Parrot : Animal 
class Elephant : Animal 
class Cow : Animal

itp.

Masz swoje metody eat (), run () itp. I wszystko jest w porządku. Pewnego dnia ktoś przychodzi i mówi - nasza klasa CageBuilder działa świetnie i korzysta z animal.weight () i animal.height (), z wyjątkiem nowego afrykańskiego żubra, który jest zbyt silny i może rozbić ścianę, więc dodam jeszcze jedna właściwość klasy Animal - isAfricanBizon () i używaj jej przy wyborze materiału i zastępuj go tylko dla klasy AfricanBizon. Następna osoba przychodzi i robi coś podobnego, a następnie wiesz, że masz wszystkie te właściwości specyficzne dla pewnego podzbioru hierarchii w klasie bazowej.

Jaki jest dobry sposób na ulepszenie / refaktoryzację takiego kodu? Jedną z możliwości jest tutaj użycie dynamicznych podcastów, aby sprawdzić typy, ale to zaśmieca osoby dzwoniące i dodaje mnóstwo „jeśli-to-jeszcze” w każdym miejscu. Możesz mieć bardziej szczegółowe interfejsy tutaj, ale jeśli wszystko, co masz, to odwołanie do klasy podstawowej, które też niewiele pomaga. Jakieś inne sugestie? Przykłady?

Dzięki!

naumcho
źródło
@James: wtedy będziesz musiał pisać parsery ręcznie. : S
Matteo Italia
5
Oczywiście jest to przypadek niedorzecznych wymagań klienta. W Afryce nie ma żubra. Nie można projektować modeli obiektów, które nie mają związku z rzeczywistością. Chyba że rzeczywistość jest tworzona przez ręce pełne dolarów. Co rozwiązuje problem.
Hans Passant
1
Zjadasz cały żubr? [Opublikowałem to wcześniej, ale z jakiegoś powodu zostało usunięte, prawdopodobnie przez pozbawione humoru jackassów.]
James McNellis,
Czy CageBuilder potrzebuje własnej klasy? Co jeśli istnieje domyślna metoda MakeCage, która może zostać zastąpiona przez każdą indywidualną klasę.
Job
1
Wspominasz o bałaganie if-then-else jako wadę dla dzwoniących, ale gdy tylko dzwoniący zaczną używać isAfricanBizon (), automatycznie zaśmiecają kod if-then-else. Więc jest to albo bałagan za pomocą isAfricanBizon () lub jeśli zaśmiecają go dynamiczne rzuty.
davidk01

Odpowiedzi:

13

Wygląda na to, że problem polega na tym, że zamiast implementować RequireConcreteWall (), zaimplementowali wywołanie flagi IsAfricanBison (), a następnie przestawili logikę, czy ściana powinna się zmieniać poza zakresem klasy. Twoje klasy powinny ujawniać zachowanie i wymagania, a nie tożsamość; Twoi konsumenci tych klas powinni pracować na podstawie tego, co im powiedziano, a nie na podstawie tego, czym są.

Josh
źródło
1
-1: Mówi tylko, czego nie robić. PO już wie, że to był zły pomysł, stąd pytanie.
Steven Evers,
12

isAfricanBizon () nie jest ogólny. Załóżmy, że rozszerzysz swoją farmę z hipopotamem, który jest również zbyt silny, ale powracający z funkcji isAfricanBizon (), aby mieć odpowiedni efekt, byłoby po prostu głupie.

zawsze chcesz dodać do interfejsu metody, które odpowiadają na konkretne pytanie, w tym przypadku byłoby to coś w rodzaju siły ()

Karoly Horvath
źródło
+1: Wydaje się, że wszyscy inni przełamują konceptualny model klasy (który po prostu ujmuje właściwości różnych rodzajów zwierząt), aby uwzględnić ten konkretny przypadek użycia. strengthMetodę można przeszukiwać za material.canHold(animal)pozwalając czysty sposób obsługi różnych rodzajów materiału niż ConcreteWall.
Aidan Cully
Podobało mi się podejście właściwości force () lepiej niż sugestia WymaganaConcreteWall () innych osób, ponieważ jest bardziej elastyczna, aby umożliwić przyszłe wymagania. Na początek spraw, aby klasa CageBuilder zdecydowała, które materiały są wystarczająco mocne, a następnie możesz łatwo rozszerzyć klasę o nowe materiały.
jhocking
3

Myślę, że twój problem jest następujący: masz różnych klientów biblioteki, którzy są zainteresowani tylko podzbiorem hierarchii, ale którym przekazano wskaźnik / referencję do klasy podstawowej. W rzeczywistości jest to problem, który można rozwiązać za pomocą funkcji dynamic_cast <>.

Jest kwestią zaprojektowania klientów, aby zminimalizować użycie dynamic_cast <>; powinni go użyć, aby ustalić, czy obiekt wymaga specjalnego traktowania, a jeśli tak, wykonaj wszystkie operacje na referencji rzutowanej w dół.

Jeśli masz kolekcje funkcji typu „mix-in”, które dotyczą kilku oddzielnych podhierarchii, możesz chcieć użyć wzorca interfejsu używanego w Javie i C #; mieć wirtualną klasę bazową, która jest klasą czysto wirtualną, i użyj dynamic_cast <>, aby ustalić, czy instancja zapewnia dla niej implementację.

antlersoft
źródło
1

Jedną rzeczą, którą możesz zrobić, jest zastąpienie jawnego sprawdzania typu, np. isAfricanBison()Sprawdzaniem właściwości, którymi naprawdę jesteś zainteresowany, tj isTooStrong().

hammar
źródło
1
isTooStrong () po co? Dodajesz kod specyficzny dla klatki do klasy zwierząt.
Steven Evers,
1

Zwierzęta nie powinny dbać o betonowe ściany. Może możesz to wyrazić za pomocą prostych wartości.

class Animal {
public:
  virtual ~Animal() {}
  virtual size_t height() const = 0;
  virtual size_t weight() const = 0;
  virtual bool isStrong() const = 0;
};

Cage *CreateCageFromSQL(Animal &a);
Cage *CreateCageFromOrangePeelsAndSticks(Animal &a);

Podejrzewam, że to nie jest opłacalne. Taki jest problem z przykładami zabawek.

Nigdy nie chciałbym widzieć RequConcreteWalls () ani linii i linii dynamicznych rzutów wskaźnika w każdym razie.

Jest to zwykle tanie rozwiązanie. Jest łatwy w utrzymaniu i konceptualizacji. I tak naprawdę problem mówi, że i tak jest związany z typem zwierzęcia.

class Animal {
public:
  virtual ~Animal() {}
  virtual CageBuilder *getCageBuilder() = 0;
};

Nie wyklucza to również korzystania z dzielonego kodu, tylko trochę zanieczyszcza Animal.

Ale sposób budowania klatki może być polityką innego systemu, a może masz więcej niż jeden typ budowniczego klatki na zwierzę. Istnieje wiele dziwnych i skomplikowanych kombinacji, które możesz wymyślić.

Użyłem projektowania opartego na komponentach do dobrych celów, głównym problemem jest to, że może być kłopotliwy, gdy własność Animal jest dzielona. Jak uniknąć wrzucania niszczycieli będących punktem bólu.

Double Dispatch to kolejna opcja, chociaż zawsze byłem niechętny, aby w nią wskoczyć.

Poza tym trudno odgadnąć problem.

Tom Kerr
źródło
0

Cóż, z pewnością wszystkie Zwierzęta mają nieodłączną właściwość attemptEscape(). Podczas gdy niektóre metody mogą dawać falsewynik we wszystkich scenariuszach, podczas gdy inne mogą mieć szansę opartą na heurystyce ich innych nieodłącznych cech, takich jak sizei weight. Z pewnością w pewnym momencie stanie attemptEscape()się trywialne, ponieważ z pewnością wróci true.

Obawiam się, że nie do końca rozumiem twoje pytanie ... wszystkie zwierzęta mają powiązane działania i cechy. Te, które są specyficzne dla zwierzęcia, należy wprowadzić tam, gdzie jest ono odpowiednie. Próba bezpośredniego powiązania Bizona z papugami nie jest dobrą konfiguracją i nie powinna być problemem we właściwym projekcie.


źródło
-1

Inną opcją byłoby użycie fabryki, która tworzy klatki odpowiednie dla każdego zwierzęcia. Myślę, że może być lepiej, jeśli warunki są bardzo różne dla każdego z nich. Ale jeśli jest to tylko jeden warunek RequiresConcreteWall(), zrobi to wyżej wymieniona metoda.

RocketR
źródło
-1

co powiesz na RecommendCageType () w przeciwieństwie do WymaganejConcreteWall ()

Kretynowie
źródło
-2

Dlaczego nie zrobić czegoś takiego?

class Animals { /***/ } class HeavyAnimals{} : Animals //The basic class for animals like the African Bison

Za pomocą klasy HeavyAnimals możesz stworzyć klasę African Bison, rozszerzając klasę HeavyAnimals.

Więc teraz jesteś klasą nadrzędną (Zwierzęta), której można użyć do stworzenia innej klasy podstawowej, takiej jak klasa HeavyAnimal, za pomocą której można utworzyć klasę afrykańskiego żubra i inne ciężkie zwierzęta. Dzięki African Bison masz teraz dostęp do metod i właściwości klasy Animal (jest to podstawa dla wszystkich zwierząt) oraz dostęp do klasy HeavyAnimals (jest to podstawa dla Heavy Animals)

mohhamed rafi
źródło
2
Może to działać jako mieszanka lub cecha, ale na pewno nie jako podklasa. To tylko błaga o wielokrotne dziedziczenie następnym razem, gdy potrzebna będzie inna właściwość.
Ordous