Próbując w pełni zrozumieć, jak rozwiązać wiele problemów z dziedziczeniem w Javie, mam klasyczne pytanie, które muszę wyjaśnić.
Powiedzmy mam klasę Animal
to ma sub klas Bird
i Horse
i muszę dokonać klasy Pegasus
, która rozciąga się od Bird
a Horse
ponieważ Pegasus
jest zarówno ptaków i koń.
Myślę, że to klasyczny problem z diamentami. Z tego co rozumiem, klasyczny sposób, aby rozwiązać ten jest aby Animal
, Bird
i Horse
ćwiczenia interfejsy i wdrożenie Pegasus
od nich.
Zastanawiałem się, czy istnieje inny sposób rozwiązania problemu, w którym nadal mogę tworzyć obiekty dla ptaków i koni. Gdyby istniał sposób na stworzenie zwierząt, byłoby to świetne, ale niekonieczne.
public class Pegasus extends Horse implements Flying
.Odpowiedzi:
Mógłbyś stworzyć interfejsy dla klas zwierząt (klasa w znaczeniu biologicznym), na przykład
public interface Equidae
dla koni ipublic interface Avialae
ptaków (nie jestem biologiem, więc terminy mogą być błędne).Wtedy nadal możesz utworzyć plik
i
i również
Dodawanie z komentarzy:
Aby zmniejszyć liczbę duplikatów kodu, możesz utworzyć klasę abstrakcyjną zawierającą większość wspólnego kodu zwierząt, które chcesz zaimplementować.
Aktualizacja
Chciałbym dodać jeszcze jeden szczegół. Jak zauważa Brian , jest to coś, o czym OP już wiedział.
Pragnę jednak podkreślić, że proponuję ominąć problem „wielokrotnego dziedziczenia” z interfejsami i nie polecam używania interfejsów, które reprezentują już konkretny typ (np. Bird), a raczej zachowanie (inne odnoszą się do typowanie kaczek, co też jest dobre, ale mam na myśli tylko biologiczną klasę ptaków, Avialae). Nie polecam również używania nazw interfejsów zaczynających się od dużej litery „I”, takich jak
IBird
, co nie mówi nic o tym, dlaczego potrzebny jest interfejs. Na tym polega różnica w pytaniu: skonstruuj hierarchię dziedziczenia za pomocą interfejsów, użyj klas abstrakcyjnych, gdy jest to przydatne, zaimplementuj konkretne klasy w razie potrzeby i użyj delegowania, jeśli to konieczne.źródło
AbstractHorse
, który może być również użyty do budowy Zebry lub innych zwierząt podobnych do koni.AbstractEquidae
byłoby bardziej odpowiednie niżAbstractHorse
. Byłoby dziwne, gdyby zebra przedłużała abstrakcyjnego konia. Nawiasem mówiąc, dobra odpowiedź.Duck
implementacji klasAvialae
?Istnieją dwa podstawowe podejścia do łączenia obiektów razem:
Sposób, w jaki to działa, polega na tym, że masz obiekt Animal. W ramach tego obiektu dodajesz następnie kolejne obiekty, które nadają wymagane właściwości i zachowania.
Na przykład:
Teraz
IFlier
wygląda to tak:Bird
Wygląda więc tak:Teraz masz wszystkie zalety dziedziczenia. Możesz ponownie użyć kodu. Możesz mieć kolekcję IFliers i możesz wykorzystać wszystkie inne zalety polimorfizmu itp.
Jednak masz również pełną elastyczność dzięki kompozycji. Możesz zastosować dowolną liczbę różnych interfejsów i kompozytowych klas podkładowych do każdego typu
Animal
- z taką samą kontrolą, jaką potrzebujesz, nad konfiguracją każdego bitu.Strategia Pattern alternatywne podejście do kompozycji
Alternatywnym podejściem w zależności od tego, co i jak robisz, jest posiadanie
Animal
klasy bazowej zawierającej kolekcję wewnętrzną, aby zachować listę różnych zachowań. W takim przypadku używasz czegoś bliższego Wzorcowi Strategii. To daje korzyści pod względem uproszczenia kodu (na przykładHorse
nie trzeba nic wiedzieć oQuadruped
lubHerbivore
), ale jeśli nie zastosujesz również podejścia interfejsu, stracisz wiele zalet polimorfizmu itp.źródło
getFlier()
trzeba go ponownie zaimplementować dla każdego gatunku ptaków.MathsTeacher
iEnglishTeacher
jak dziedziczenieTeacher
,ChemicalEngineer
,MaterialsEngineer
etc dziedzicząEngineer
.Teacher
iEngineer
obie implementująComponent
. ToPerson
ma po prostu listęComponent
s i możesz nadać im do tego odpowiednieComponent
sPerson
. tj.person.getComponent(Teacher.class)
,person.getComponent(MathsTeacher.class)
itd.Mam głupi pomysł:
źródło
Czy mogę zasugerować koncepcję pisania na klawiaturze ?
Najprawdopodobniej miałbyś tendencję do rozszerzania interfejsu typu Bird and a Horse na Pegaza, ale pisanie typu kaczego sugeruje, że powinieneś raczej dziedziczyć zachowanie . Jak już wspomniano w komentarzach, pegaz nie jest ptakiem, ale potrafi latać. Więc twój Pegaz powinien raczej dziedziczyć
Flyable
-interface i powiedzmyGallopable
-interface.Ten rodzaj koncepcji jest używany we wzorcu strategii . Podany przykład pokazuje, jak w rzeczywistości a dziedziczy kaczka
FlyBehaviour
aQuackBehaviour
i nadal nie może być kaczki, npRubberDuck
, który nie może latać. Mogliby równieżDuck
rozszerzyćBird
klasę a, ale wtedy zrezygnowaliby z pewnej elastyczności, ponieważ każdyDuck
mógłby latać, nawet biedniRubberDuck
.źródło
Technicznie rzecz biorąc, można rozszerzyć tylko jedną klasę naraz i zaimplementować wiele interfejsów, ale kładąc ręce na inżynierię oprogramowania, sugerowałbym raczej rozwiązanie specyficzne dla problemu, na które nie ma odpowiedzi. Nawiasem mówiąc, dobrą praktyką OO jest nie rozszerzanie konkretnych klas / tylko rozszerzanie klas abstrakcyjnych, aby zapobiec niechcianemu zachowaniu dziedziczenia - nie ma czegoś takiego jak „zwierzę” i nie ma użycia obiektu zwierzęcego, a jedynie konkretne zwierzęta.
źródło
W Javie 8, która jest nadal w fazie rozwoju od lutego 2014 r., Można użyć domyślnych metod, aby uzyskać coś w rodzaju C ++ - jak wielokrotne dziedziczenie. Możesz również rzucić okiem na ten samouczek, który pokazuje kilka przykładów, z którymi łatwiej jest rozpocząć pracę niż oficjalna dokumentacja.
źródło
Trzymanie konia w stajni z półdrzwiami jest bezpieczne, ponieważ koń nie może przejść przez półdrzwi. Dlatego założyłem usługę zakwaterowania koni, która przyjmuje dowolny przedmiot typu koń i umieszcza go w stajni z półdrzwiami.
Czy więc koń jest jak zwierzę, które potrafi latać nawet koń?
Kiedyś dużo myślałem o dziedziczeniu wielokrotnym, ale teraz, gdy programuję od ponad 15 lat, nie obchodzi mnie już wdrażanie wielokrotnego dziedziczenia.
Najczęściej, kiedy próbowałem poradzić sobie z projektem, który wskazywał na wielokrotne dziedziczenie, później przychodziłem do wiadomości, że tęskniłem za zrozumieniem domeny problemu.
LUB
źródło
Java nie ma problemu z dziedziczeniem wielokrotnym, ponieważ nie ma dziedziczenia wielokrotnego. Jest to zgodne z projektem, aby rozwiązać rzeczywisty problem wielokrotnego dziedziczenia (problem z diamentami).
Istnieją różne strategie łagodzenia tego problemu. Najbardziej natychmiast osiągalnym jest obiekt Composite, który sugeruje Pavel (zasadniczo jak radzi sobie z tym C ++). Nie wiem, czy wielokrotne dziedziczenie przez linearyzację C3 (lub podobne) jest na kartach przyszłości Javy, ale wątpię w to.
Jeśli twoje pytanie jest akademickie, to poprawnym rozwiązaniem jest to, że Ptak i Koń są bardziej konkretne i fałszywe jest założenie, że Pegaz to po prostu ptak i koń razem wzięci. Bardziej poprawne byłoby stwierdzenie, że Pegaz ma pewne nieodłączne właściwości wspólne z Ptakami i Koniami (to znaczy, że mogą mieć wspólnych przodków). Można to wystarczająco modelować, jak wskazuje odpowiedź Moritza.
źródło
Myślę, że to zależy w dużej mierze od twoich potrzeb i tego, jak twoje klasy zwierząt mają być użyte w twoim kodzie.
Jeśli chcesz mieć możliwość korzystania z metod i funkcji implementacji Horse i Bird w klasie Pegasus, możesz zaimplementować Pegaza jako kompozycję Bird and a Horse:
Inną możliwością jest użycie systemu jednostka-składnik zamiast dziedziczenia do definiowania zwierząt. Oczywiście oznacza to, że nie będziesz mieć indywidualnych klas Java zwierząt, ale zamiast tego będą one definiowane tylko przez ich komponenty.
Niektóre pseudokody dla podejścia Entity-Component-System mogą wyglądać następująco:
źródło
możesz mieć hierarchię interfejsów, a następnie rozszerzać swoje klasy z wybranych interfejsów:
a następnie zdefiniuj swoje klasy zgodnie z potrzebami, rozszerzając określony interfejs:
źródło
IBird
iIHorse
powinien wdrożyćIAnimal
zamiastAnimal
Ehm, twoja klasa może być podklasą tylko dla 1 innej, ale nadal możesz mieć zaimplementowanych tyle interfejsów, ile chcesz.
Pegaz to w rzeczywistości koń (jest to szczególny przypadek konia), który potrafi latać (co jest „umiejętnością” tego specjalnego konia). Z drugiej strony można powiedzieć, że Pegaz to ptak, który potrafi chodzić i jest czteronożny - wszystko zależy od tego, jak łatwiej będzie Ci napisać kod.
Tak jak w Twoim przypadku możesz powiedzieć:
źródło
Interfejsy nie symulują wielokrotnego dziedziczenia. Twórcy Javy uważali, że wielokrotne dziedziczenie jest błędne, więc w Javie nie ma czegoś takiego.
Jeśli chcesz połączyć funkcjonalność dwóch klas w jedną - skorzystaj z kompozycji obiektów. To znaczy
A jeśli chcesz ujawnić pewne metody, zdefiniuj je i pozwól im delegować wywołanie do odpowiedniego kontrolera.
Tutaj mogą się przydać interfejsy - jeśli
Component1
implementuje interfejsInterface1
iComponent2
implementujeInterface2
, możesz zdefiniowaćAbyś mógł używać obiektów zamiennie tam, gdzie pozwala na to kontekst.
Więc z mojego punktu widzenia nie możesz mieć problemu z diamentami.
źródło
Jak już wiesz, wielokrotne dziedziczenie klas w Javie nie jest możliwe, ale jest możliwe w przypadku interfejsów. Możesz również rozważyć użycie wzorca projektowego kompozycji.
Kilka lat temu napisałem bardzo obszerny artykuł o kompozycji ...
/codereview/14542/multiple-inheritance-and-composition-with-java-and-c-updated
źródło
Spójrz na poniższy przykład, aby lepiej zrozumieć
Kiedy używać wzoru dekoratora?
źródło
Aby zmniejszyć złożoność i uprościć język, w Javie nie jest obsługiwane dziedziczenie wielokrotne.
Rozważmy scenariusz, w którym A, B i C to trzy klasy. Klasa C dziedziczy klasy A i B. Jeśli klasy A i B mają tę samą metodę i wywołujesz ją z obiektu klasy potomnej, wywołanie metody klasy A lub B będzie niejednoznaczne.
Ponieważ błędy czasu kompilacji są lepsze niż błędy czasu wykonania, java renderuje błąd czasu kompilacji, jeśli odziedziczysz 2 klasy. Więc niezależnie od tego, czy masz tę samą metodę, czy inną, teraz wystąpi błąd czasu kompilacji.
źródło
Do rozwiązania problemu wielokrotnego dziedziczenia w Javie używany jest → interfejs
J2EE (core JAVA) Uwagi Mr. KVR Strona 51
źródło