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!
źródło
Odpowiedzi:
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ą.
źródło
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 ()
źródło
strength
Metodę można przeszukiwać zamaterial.canHold(animal)
pozwalając czysty sposób obsługi różnych rodzajów materiału niżConcreteWall
.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ę.
źródło
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, tjisTooStrong()
.źródło
Zwierzęta nie powinny dbać o betonowe ściany. Może możesz to wyrazić za pomocą prostych wartości.
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.
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.
źródło
Cóż, z pewnością wszystkie Zwierzęta mają nieodłączną właściwość
attemptEscape()
. Podczas gdy niektóre metody mogą dawaćfalse
wynik we wszystkich scenariuszach, podczas gdy inne mogą mieć szansę opartą na heurystyce ich innych nieodłącznych cech, takich jaksize
iweight
. Z pewnością w pewnym momencie stanieattemptEscape()
się trywialne, ponieważ z pewnością wrócitrue
.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
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.źródło
co powiesz na RecommendCageType () w przeciwieństwie do WymaganejConcreteWall ()
źródło
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)
źródło