Próbuję nauczyć się inżynierii oprogramowania i napotykam sprzeczne informacje, które mnie dezorientują.
Uczyłem się OOP i czym są abstrakcyjne klasy / interfejsy i jak z nich korzystać, ale potem czytam, że należy „preferować kompozycję zamiast dziedziczenia”. Rozumiem, że skład ma miejsce, gdy jedna klasa tworzy / tworzy obiekt innej klasy w celu wykorzystania / interakcji z funkcjonalnością tego nowego obiektu.
Więc moje pytanie brzmi ... Czy mam nie używać abstrakcyjnych klas i interfejsów? Aby nie tworzyć klasy abstrakcyjnej i rozszerzać / dziedziczyć funkcji tej klasy abstrakcyjnej w konkretnych klasach, a zamiast tego po prostu komponować nowe obiekty, aby korzystać z funkcji drugiej klasy?
Czy też mam użyć kompozycji ORAZ odziedziczyć po klasach abstrakcyjnych; używasz ich obu w połączeniu ze sobą? Jeśli tak, czy mógłbyś podać kilka przykładów tego, jak to by działało i jakie są jego zalety?
Ponieważ czuję się najlepiej z PHP, używam go do ulepszania moich umiejętności OOP / SE przed przejściem do innych języków i przenoszeniem nowo nabytych umiejętności SE, więc przykłady użycia PHP byłyby bardzo mile widziane.
źródło
Odpowiedzi:
Składanie nad dziedziczeniem oznacza, że jeśli chcesz ponownie użyć lub rozszerzyć funkcjonalność istniejącej klasy, często bardziej odpowiednie jest utworzenie innej klasy, która „zawinie” istniejącą klasę i użyje jej implementacji wewnętrznie. Wzór dekoratora jest tego przykładem.
Dziedziczenie nie jest dobrym domyślnym sposobem radzenia sobie ze scenariuszami ponownego użycia, ponieważ często chcesz użyć tylko części funkcji klasy podstawowej, a podklasa nie może obsługiwać całego kontraktu klasy podstawowej w sposób, który spełnia podstawienie Liskowa zasada .
Metoda szablonów jest dobrym przykładem przypadku, w którym dziedziczenie jest odpowiednie.
Zobacz tę odpowiedź, aby uzyskać wskazówki dotyczące wyboru między kompozycją a spadkiem.
źródło
Nie znam się na PHP, aby podać konkretne przykłady w tym języku, ale oto kilka wskazówek, które uznałem za przydatne.
Interfejsy / cechy są przydatne do definiowania zachowania w wielu klasach, które mogą mieć bardzo mało wspólnego.
Na przykład, jeśli pracujesz nad interfejsem JSON, możesz zdefiniować interfejs, który określa dwie metody: „toJson” i „toStatusCode”. Umożliwiłoby to napisanie pomocnika, który może wziąć dowolny obiekt implementujący ten interfejs i przekształcić go w odpowiedź HTTP.
W większości przypadków jest to lepsze niż dziedziczenie bezpośrednie, ponieważ jest mniej prawdopodobne, że cierpi z powodu delikatnego problemu z klasą podstawową, ponieważ dąży do płytkich hierarchii klas.
Bezpośrednie dziedziczenie najlepiej jest traktować jako coś, co robisz, gdy istnieją ku temu ważne powody, a nie coś, co robisz, chyba że istnieją ku temu ważne powody.
W językach, które nie obsługują domyślnych implementacji w interfejsach, rozsądnym sposobem może być współdzielenie zestawu podstawowych funkcji, ale zwykle mocno ogranicza to, co można zrobić z podklasami (wielokrotne dziedziczenie, jeśli jest dostępne, rzadko jest warte bólu) . Często uważam, że warto zaryzykować napisanie metod przekierowania na płytę kotłową, aby zamiast tego użyć kompozycji.
Kompozycja powinna być twoją domyślną strategią. W miarę możliwości komponuj z interfejsami i przesyłaj je jako argumenty konstruktora. To znacznie ułatwi Ci pisanie testów.
Unikaj pracy z globalnymi singletonami w jak największym stopniu, ponieważ porównywanie oczekiwanych i rzeczywistych danych wyjściowych jest właściwym problemem, jeśli część danych wyjściowych zależy od stanu zegara systemowego.
Nie zawsze jest to oczywiście możliwe. Na przykład platforma internetowa Play udostępnia bibliotekę JSON, która jest w zasadzie językiem specyficznym dla domeny. Zmiana tego byłaby dużym przedsięwzięciem, którego zastąpienie wezwań do „Json.prettyPrint” byłoby tylko bardzo niewielką częścią.
źródło
Składanie ma miejsce, gdy klasa oferuje pewną funkcjonalność, tworząc instancję (być może wewnętrzną) klasy, która już implementuje tę funkcjonalność, zamiast dziedziczenia po tej klasie.
Na przykład, jeśli masz klasę, która modeluje statek, a powiedziano ci teraz, że twój statek powinien oferować lądowisko dla helikopterów, nie jest naturalne wyprowadzanie statku z lądowiska dla helikopterów (duh!), Zamiast tego powinieneś twój statek zawiera klasę lądowisk dla helikopterów i wystawiasz go jakąś
Ship.getHelipad()
metodą.W dawnych latach (mniej więcej dziesięć lat temu) ludzie postrzegali dziedziczenie jako szybki i łatwy sposób na agregowanie funkcjonalności, więc było wiele przykładów rodzaju „statku dziedziczącego po lądowisku dla helikopterów”, które były oczywiście bardzo kiepskie.
Ale powiedzenie „Preferuj kompozycję nad dziedziczeniem” zostało starannie sformułowane, aby wyjaśnić, że jest to tylko sugestia, a nie reguła. Autor powiedzenia był na tyle ostrożny, by powstrzymać się od powiedzenia czegoś w rodzaju „ nigdy nie będziesz używać dziedzictwa, tylko kompozycji”. Zasadniczo zwraca to uwagę społeczności inżynierów oprogramowania na fakt, że dziedziczenie zostało nadużywane, podczas gdy w wielu przypadkach kompozycja zapewnia bardziej przejrzyste, eleganckie i łatwe do utrzymania projekty niż dziedziczenie.
Zasadniczo więc powiedzenie „Faworyzuj kompozycję nad dziedziczeniem” sugeruje, że ilekroć masz do czynienia z „dziedziczeniem czy komponowaniem”? pytanie, powinieneś mocno przemyśleć, jaka jest najbardziej odpowiednia strategia, i że istnieje największe prawdopodobieństwo, że najbardziej odpowiednią strategią okaże się kompozycja, a nie dziedziczenie.
Ale ponieważ nie jest to regułą, należy również pamiętać, że w wielu przypadkach dziedziczenie jest bardziej naturalne. Jeśli użyjesz kompozycji tam, gdzie powinieneś był użyć dziedziczenia, wiele zła spotka Twój kod.
Wracając do przykładu statku, jeśli twój statek musi oferować interfejs do interakcji z a
FloatingMachine
, bardziej naturalne jest wyprowadzenie go zFloatingMachine
klasy abstrakcyjnej , która z kolei prawdopodobnie mogłaby pochodzić z innejMachine
klasy abstrakcyjnej .Oto ogólna zasada dla odpowiedzi na pytanie dotyczące składu a pytanie dotyczące dziedziczenia:
Statek „to„ maszyna pływająca, a maszyna pływająca ”to„ maszyna. Tak więc dziedziczenie jest dla nich w porządku. Ale statek nie jest oczywiście lądowiskiem dla helikopterów. Lepiej więc skomponuj funkcjonalność lądowiska dla helikopterów.
źródło