Masz już kilka fajnych odpowiedzi, ale ogromny słoń w pokoju w twoim pytaniu to:
usłyszałem od kogoś, że należy unikać dziedziczenia i zamiast tego powinniśmy używać interfejsów
Zasadą jest, że gdy ktoś daje ci praktyczną regułę, zignoruj ją. Dotyczy to nie tylko „ktoś ci coś mówi”, ale także czytania rzeczy w Internecie. O ile nie wiesz, dlaczego (i naprawdę możesz za tym stać), takie porady są bezwartościowe i często bardzo szkodliwe.
Z mojego doświadczenia, najważniejszymi i pomocnymi pojęciami w OOP są „niskie sprzężenie” i „wysoka kohezja” (klasy / obiekty wiedzą o sobie jak najmniej, a każda jednostka jest odpowiedzialna za tak mało rzeczy, jak to możliwe).
Niskie sprzężenie
Oznacza to, że każdy „pakiet rzeczy” w twoim kodzie powinien jak najmniej zależeć od jego otoczenia. Dotyczy to klas (projektowanie klas), ale także obiektów (faktyczna implementacja), ogólnie „plików” (tj. Liczby #include
s na pojedynczy .cpp
plik, liczby import
na pojedynczy).java
plik i tak dalej).
Znakiem, że dwa byty są sprzężone, jest to, że jeden z nich się zepsuje (lub będzie musiał zostać zmieniony), gdy drugi zostanie zmieniony w jakikolwiek sposób.
Dziedziczenie oczywiście zwiększa sprzężenie; zmiana klasy podstawowej powoduje zmianę wszystkich podklas.
Interfejsy zmniejszają sprzężenie: definiując przejrzystą umowę opartą na metodach, możesz dowolnie zmieniać dowolne ustawienia obu stron interfejsu, o ile nie zmienisz umowy. (Uwaga: „interfejs” to ogólna koncepcja, Javainterface
klasy abstrakcyjne lub C ++ to tylko szczegóły implementacji).
Wysoka spójność
Oznacza to, że każda klasa, obiekt, plik itp. Musi zajmować się możliwie najmniejszą odpowiedzialnością. Tj. Unikaj dużych klas, które robią wiele rzeczy. W twoim przykładzie, jeśli twoja broń ma całkowicie odrębne aspekty (amunicja, zachowanie podczas strzelania, przedstawienie graficzne, przedstawienie zapasów itp.), Możesz mieć różne klasy, które reprezentują dokładnie jedną z tych rzeczy. Główna klasa broni następnie przekształca się w „posiadacza” tych szczegółów; obiekt broni jest więc niewiele więcej niż kilkoma wskazówkami do tych szczegółów.
W tym przykładzie upewnisz się, że twoja klasa reprezentująca „Zachowanie podczas strzelania” wie jak najmniej po ludzku o głównej klasie broni. Optymalnie nic. Oznaczałoby to na przykład, że możesz dać „Zachowanie podczas strzelania” każdemu obiektowi w twoim świecie (wieżyczki, wulkany, postacie ...) jednym dotknięciem palca. Jeśli w pewnym momencie chcesz zmienić sposób reprezentowania broni w ekwipunku, możesz to po prostu zrobić - tylko twoja klasa ekwipunku w ogóle o tym wie.
Znakiem, że istota nie jest spójna, jest to, że rośnie i rośnie, rozgałęzia się w kilku kierunkach jednocześnie.
Dziedziczenie, jak to opisujesz, zmniejsza spójność - twoje klasy broni to w końcu duże kawałki, które zajmują się różnymi rodzajami niezwiązanymi z twoją bronią.
Interfejsy pośrednio zwiększają spójność, wyraźnie rozdzielając obowiązki między dwie strony interfejsu.
Co zrobić teraz
Nadal nie ma twardych i szybkich zasad, wszystko to tylko wytyczne. Ogólnie rzecz biorąc, jak wspomniał użytkownik TKK w swojej odpowiedzi, dziedziczenie uczy się dużo w szkole i książkach; to fantazyjne rzeczy o OOP. Interfejsy są prawdopodobnie bardziej nudne w nauczaniu, a także (jeśli pominąć trywialne przykłady) nieco trudniejsze, otwierając pole wstrzykiwania zależności, które nie jest tak jednoznaczne jak dziedziczenie.
Pod koniec dnia schemat oparty na dziedziczeniu jest nadal lepszy niż brak wyraźnego projektu OOP. Więc nie krępuj się. Jeśli chcesz, możesz trochę przemyśleć / google o niskim sprzężeniu, wysokiej kohezji i sprawdzić, czy chcesz dodać tego rodzaju myślenie do swojego arsenału. Zawsze możesz ponownie to sprawdzić, jeśli chcesz, później; lub wypróbuj podejścia oparte na interfejsie w swoim następnym większym nowym module kodu.