Czy powinienem unikać używania dziedziczenia obiektów w celu opracowania gry?

36

Wolę funkcje OOP podczas tworzenia gier w Unity. Zwykle tworzę klasę podstawową (głównie abstrakcyjną) i używam dziedziczenia obiektów, aby udostępniać tę samą funkcjonalność różnym innym obiektom.

Jednak niedawno usłyszałem od kogoś, że należy unikać dziedziczenia i zamiast tego powinniśmy używać interfejsów. Zapytałem więc dlaczego, a on powiedział, że „Dziedziczenie obiektów jest oczywiście ważne, ale jeśli istnieje wiele rozszerzonych obiektów, relacje między klasami są głęboko powiązane.

Używam abstrakcyjną klasę bazową, coś podobnego WeaponBasei tworzenie konkretnych klas broni jak Shotgun, AssaultRifle, Machineguncoś takiego. Jest tak wiele zalet, a jednym wzorem, który naprawdę lubię, jest polimorfizm. Mogę traktować obiekt utworzony przez podklasy tak, jakby były klasą podstawową, dzięki czemu logikę można drastycznie zredukować i uczynić bardziej użyteczną. Jeśli muszę uzyskać dostęp do pól lub metod podklasy, rzutuję z powrotem do podklasy.

Nie chcę, aby zdefiniować same pola, powszechnie stosowane w różnych klasach, jak AudioSource/ Rigidbody/ Animatori wiele pól członkowskich określonych jak ja fireRate, damage, range, spreadi tak dalej. Również w niektórych przypadkach niektóre metody mogą zostać zastąpione. Dlatego używam w tym przypadku metod wirtualnych, więc w zasadzie mogę wywołać tę metodę przy użyciu metody z klasy nadrzędnej, ale jeśli logika powinna być inna w przypadku dziecka, ręcznie je przesłoniłem.

Czy zatem wszystkie te rzeczy należy porzucić jako złe praktyki? Czy zamiast tego powinienem używać interfejsów?

moderator
źródło
30
„Jest tak wiele zalet, a jednym wzorem, który naprawdę lubię, jest polimorfizm”. Polimorfizm nie jest wzorem. To dosłownie cały punkt OOP. Inne rzeczy, takie jak wspólne pola itp., Można łatwo osiągnąć bez powielania kodu lub dziedziczenia.
Cubic
3
Czy osoba, która zaproponowała ci interfejsy, była lepsza od dziedziczenia, przedstawiła ci jakieś argumenty? Jestem ciekawy.
Hawker65
1
@Cubic Nigdy nie powiedziałem, że polimorfizm jest wzorem. Powiedziałem „… jednym z wzorów, którym naprawdę podoba mi się polimorfizm”. Oznacza to, że wzór, który naprawdę mi się podoba, używa „polimorfizmu”, a nie polimorfizm jest wzorem.
moderator
4
Ludzie reklamują używanie interfejsów zamiast dziedziczenia w dowolnej formie rozwoju. Jednak ma tendencję do dodawania dużych kosztów ogólnych, dysonansu poznawczego, prowadzi do eksplozji klasowej, często dodaje wątpliwej wartości i zwykle nie dobrze współdziała w systemie, chyba że KAŻDY w projekcie podąża za koncentracją na interfejsach. Więc nie rezygnuj jeszcze z dziedziczenia. Jednak gry mają pewne unikalne cechy, a architektura Unity oczekuje, że zastosujesz paradygmat projektowania CES, dlatego powinieneś dostosować swoje podejście. Przeczytaj serię Wizards and Warriors Erica Lipperta opisującą problemy związane z typem gry.
Dunk
2
Powiedziałbym, że interfejs jest szczególnym przypadkiem dziedziczenia, w którym klasa podstawowa nie zawiera pól danych. Myślę, że sugeruje się, aby nie budować ogromnego drzewa, w którym każdy obiekt musi zostać sklasyfikowany, ale zamiast tego wiele małych drzewek opartych na interfejsach, w których można skorzystać z wielokrotnego dziedziczenia. Polimorfizm nie jest gubiony w interfejsach.
Emanuele Paolini

Odpowiedzi:

65

Preferuj kompozycję zamiast dziedziczenia w twojej jednostce i systemach zapasów / przedmiotów. Ta rada ma zwykle zastosowanie do struktur logiki gry, gdy sposób, w jaki można składać złożone rzeczy (w czasie wykonywania), może prowadzić do wielu różnych kombinacji; wtedy wolimy kompozycję.

Preferuj dziedziczenie nad kompozycją w logice na poziomie aplikacji, od konstrukcji interfejsu użytkownika po usługi. Na przykład,

Widget->Button->SpinnerButton lub

ConnectionManager->TCPConnectionManagervs ->UDPConnectionManager.

... istnieje tutaj jasno określona hierarchia wyprowadzania, a nie mnogość potencjalnych wyprowadzeń, więc po prostu łatwiej jest zastosować dziedziczenie.

Konkluzja : użyj dziedziczenia tam, gdzie możesz, ale użyj kompozycji tam, gdzie musisz. PS Innym powodem, dla którego możemy preferować kompozycję w systemach encji, jest to, że zwykle istnieje wiele encji, a dziedziczenie może ponieść koszty wydajności w celu uzyskania dostępu do elementów na każdym obiekcie; patrz vtables .

Inżynier
źródło
10
Dziedziczenie nie oznacza, że ​​wszystkie metody byłyby wirtualne. Dlatego nie prowadzi to automatycznie do obniżenia wydajności. VTable odgrywa rolę tylko w wysyłaniu wirtualnych połączeń, a nie w uzyskiwaniu dostępu do członków danych.
Ruslan
1
@ Ruslan Dodałem słowa „może” i „może”, aby uwzględnić Twój komentarz. W przeciwnym razie, aby zachować zwięzłość odpowiedzi, nie wdałem się w zbyt wiele szczegółów. Dzięki.
Inżynier
7
P.S. The other reason we may favour composition in entity systems is ... performance: Czy to naprawdę prawda? Patrząc na stronę WIkipedia połączoną z @Linaith, widać, że trzeba skomponować obiekt interfejsów. W związku z tym masz (nadal lub nawet więcej) wywołań funkcji wirtualnych i więcej braków pamięci podręcznej, ponieważ wprowadziłeś kolejny poziom pośredni.
Flamefire
1
@Flamefire Tak, to prawda; istnieją sposoby organizowania danych w taki sposób, aby wydajność pamięci podręcznej była optymalna, niezależnie, a na pewno znacznie bardziej optymalna niż te dodatkowe pośrednie. Nie są to dziedziczenia i są one kompozycjami, aczkolwiek bardziej w sensie tablic struktury-struktury-struktury-tablic (przeplecione / nieprzeplecione dane w układzie liniowym pamięci podręcznej), dzielenie na osobne tablice i czasami duplikowanie danych w celu dopasowania zestawów operacji w różnych częściach rurociągu. Ale jeszcze raz, wchodząc w to tutaj, byłaby bardzo ważna dywersja, której najlepiej unikać ze względu na zwięzłość.
inżynier
2
Pamiętaj, aby zachować komentarze do cywilnych wniosków o wyjaśnienia lub krytykę oraz debatować nad treściami pomysłów, a nie z charakterem lub doświadczeniem osoby , która je wypowiada.
Josh
18

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 #includes na pojedynczy .cppplik, liczby importna 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.

AnoE
źródło
Dziękuję za szczegółowe wyjaśnienie. Naprawdę pomocne jest zrozumienie tego, czego mi brakuje.
moderator
13

Pomysł, że należy unikać dziedziczenia, jest po prostu błędny.

Istnieje zasada kodowania o nazwie Kompozycja nad dziedziczeniem . Mówi, że możesz osiągnąć te same rzeczy za pomocą kompozycji i jest to preferowane, ponieważ możesz ponownie wykorzystać część kodu. Zobacz, dlaczego wolę kompozycję niż dziedziczenie?

Muszę powiedzieć, że podoba mi się twoja klasa broni i czy zrobiłbym to samo. Ale do tej pory nie stworzyłem gry ...

Jak zauważył James Trotter, kompozycja może mieć pewne zalety, zwłaszcza jeśli chodzi o elastyczność w czasie wykonywania, aby zmienić sposób działania broni. Byłoby to możliwe dzięki dziedziczeniu, ale jest trudniejsze.

Linaith
źródło
14
Zamiast mieć klasy dla broni, możesz mieć pojedynczą klasę „broni” i komponenty do obsługi tego, co robi ostrzał, jakie ma załączniki i co robią, jakie ma „magazyn amunicji”, możesz zbudować wiele konfiguracji , z których niektóre mogą być „strzelbą” lub „karabinem szturmowym”, ale można to również zmienić w czasie wykonywania, na przykład w celu odłączenia załączników i zmiany pojemności magazynka itp. Można też stworzyć zupełnie nowe konfiguracje broni nawet o tym nie myślałem. Tak, możesz osiągnąć coś podobnego z dziedziczeniem, ale nie tak łatwo.
Trotski94,
3
@JamesTrotter W tym momencie myślę o Borderlands i jego działach i zastanawiam się, jak potworna byłaby to tylko dziedziczenie ...
Baldrickk
1
@JamesTrotter Chciałbym, żeby to była odpowiedź, a nie komentarz.
Pharap
6

Problem polega na tym, że dziedziczenie prowadzi do sprzężenia - twoje obiekty muszą wiedzieć o sobie więcej. Dlatego reguła brzmi: „Zawsze preferuj kompozycję nad dziedziczeniem”. To nie znaczy, NIGDY nie używaj dziedziczenia, oznacza to, że używaj go tam, gdzie jest to całkowicie właściwe, ale jeśli kiedykolwiek tam siedzisz i myślisz: „Mogę to zrobić w obie strony i oba mają sens”, po prostu przejdź do kompozycji.

Dziedziczenie może być również swego rodzaju ograniczeniem. Masz „WeaponBase”, który może być AssultRifle - niesamowity. Co się dzieje, gdy masz strzelbę z podwójną lufą i chcesz pozwolić lufom strzelać niezależnie - trochę trudniej, ale wykonalnie, masz klasę jednej lufy i podwójnej lufy, ale nie możesz po prostu zamontować 2 pojedynczych luf na tym samym pistolecie, prawda? Czy możesz zmodyfikować jedną, aby mieć 3 baryłki, czy potrzebujesz do tego nowej klasy?

Co z AssultRifle z GrenadeLauncher pod spodem - hmm, trochę trudniej. Czy możesz zastąpić GrenadeLauncher latarką do nocnych polowań?

Wreszcie, w jaki sposób pozwalasz użytkownikowi tworzyć poprzednie pistolety, edytując plik konfiguracyjny? Jest to trudne, ponieważ masz zakodowane relacje, które mogą być lepiej skomponowane i skonfigurowane z danymi.

Wielokrotne dziedziczenie może nieco naprawić niektóre z tych trywialnych przykładów, ale dodaje własny zestaw problemów.

Zanim pojawiły się popularne powiedzenia, takie jak „Wolę kompozycję niż dziedziczenie”, dowiedziałem się o tym, pisząc zbyt skomplikowane drzewo dziedziczenia (co było niesamowitą zabawą i działało idealnie), a następnie stwierdziłem, że naprawdę trudno było je później modyfikować i utrzymywać. To powiedzenie jest po prostu, abyś miał alternatywny i zwarty sposób na naukę i zapamiętywanie takiej koncepcji bez konieczności przechodzenia przez całe doświadczenie - ale jeśli chcesz intensywnie korzystać z dziedziczenia, zalecam zachować otwarty umysł i ocenić, jak dobrze działa. dla ciebie - nic strasznego się nie wydarzy, jeśli będziesz intensywnie korzystał z dziedziczenia, a w najgorszym przypadku może to być wspaniałe doświadczenie edukacyjne (w najlepszym przypadku może być dla ciebie dobre)

Bill K.
źródło
2
„w jaki sposób pozwalasz użytkownikowi tworzyć poprzednie pistolety, edytując plik konfiguracyjny?” -> Wzór, który generalnie tworzę, tworzy ostateczną klasę dla każdej broni. Na przykład jak Glock17. Najpierw utwórz klasę WeaponBase. Klasa ta jest abstrakcyjna, definiuje wspólne wartości, takie jak tryb ognia, szybkostrzelność, rozmiar magazynka, maksymalna amunicja, śrut. Obsługuje także dane wejściowe użytkownika, takie jak mouse1 / mouse2 / reload i wywołuje odpowiednią metodę. Są prawie wirtualne lub podzielone na więcej funkcji, dzięki czemu deweloper może w razie potrzeby zastąpić podstawowe funkcje. A potem stwórz kolejną klasę, która rozszerzyła WeaponBase,
moderator
2
coś w stylu SlideStoppableWeapon. Większość pistoletów ma to, a to obsługuje dodatkowe zachowania, takie jak zatrzymanie przesuwu. Na koniec stwórz klasę Glock17, która rozciąga się od SlideStoppableWeapon. Glock17 to klasa ostateczna, jeśli broń ma unikalną zdolność, którą tylko ma, w końcu tutaj zapisaną. Zwykle używam tego wzoru, gdy broń ma specjalny mechanizm ognia lub mechanizm przeładowania. Zaimplementuj to unikalne zachowanie do końca hierarchii klas, łącz jak najwięcej wspólnych logiki od rodziców.
moderator
2
@modernator Jak już powiedziałem, jest to tylko wskazówka - jeśli nie ufasz, możesz zignorować to, po prostu miej oczy otwarte na wyniki w czasie i oceniaj co jakiś czas korzyści. Próbuję zapamiętać miejsca, w które zatarłem palce u nóg i pomagam innym tego uniknąć, ale to, co działa dla mnie, może nie działać dla ciebie.
Bill K
7
@modernator W Minecraft stworzyli Monsterpodklasę WalkingEntity. Potem dodali szlam. Jak myślisz, jak dobrze to zadziałało?
user253751,
1
@immibis Czy masz odniesienie lub anegdotyczny link do tego problemu? Googling słowa kluczowe minecraft zagłuszają mnie w linkach wideo ;-)
Peter - Przywróć Monikę
3

W przeciwieństwie do innych odpowiedzi, nie ma to nic wspólnego z dziedziczeniem a kompozycją. Dziedziczenie a kompozycja to decyzja, którą podejmujesz odnośnie tego, jak klasa zostanie zaimplementowana. Interfejsy kontra klasy to decyzja, która pojawia się wcześniej.

Interfejsy są pierwszorzędnymi obywatelami dowolnego języka OOP. Klasy są dodatkowymi szczegółami implementacji. Umysły nowych programistów są poważnie wypaczone przez nauczycieli i książki, które wprowadzają klasy i dziedziczenie przed interfejsami.

Kluczową zasadą jest to, że tam, gdzie to możliwe, typami wszystkich parametrów metody i zwracanych wartości powinny być interfejsy, a nie klasy. Dzięki temu interfejsy API są znacznie bardziej elastyczne i wydajne. Zdecydowana większość czasu, twoje metody i osoby wywołujące nie powinny mieć powodu, aby wiedzieć lub dbać o rzeczywiste klasy obiektów, z którymi mają do czynienia. Jeśli Twój interfejs API nie zgadza się ze szczegółami implementacji, możesz swobodnie przełączać się między kompozycją a dziedziczeniem, nie niszcząc niczego.

Nie ty
źródło
Jeśli interfejsy są pierwszorzędnymi obywatelami dowolnego języka OOP, zastanawiam się, czy Python, Ruby… nie są językami OOP.
BlackJack
@BlackJack: W Ruby, interfejs jest niejawna (kaczka wpisując jeśli będzie), a wyrażone za pomocą kompozycji zamiast dziedziczenia (również, że ma wstawek, które są gdzieś pomiędzy nimi, o ile mi wiadomo) ...
AnoE
@BlackJack „Interfejs” (koncepcja) nie wymaga interface(słowa kluczowego języka).
Caleth
2
@Caleth Ale nazywanie ich pierwszorzędnymi obywatelami robi IMHO. Kiedy opis języka twierdzi, że coś jest obywatelem pierwszej klasy , zwykle oznacza to, że jest to prawdziwa rzecz w tym języku, który ma typ i wartość i może być przekazywany. Podobnie jak klasy są pierwszorzędnymi obywatelami Pythona, podobnie jak funkcje, metody i moduły. Jeśli mówimy o tej koncepcji, to interfejsy są również częścią wszystkich języków innych niż OOP.
BlackJack
2

Ilekroć ktoś mówi ci, że jedno konkretne podejście jest najlepsze we wszystkich przypadkach, jest to to samo, co mówienie, że ten sam lek leczy wszystkie choroby.

Dziedziczenie a kompozycja jest pytaniem typu „czy przeciw”. Interfejsy to kolejny (trzeci) sposób, odpowiedni w niektórych sytuacjach.

Dziedziczenie, czyli logika: używasz go, gdy nowa klasa ma się zachowywać, i używasz go zupełnie jak (na zewnątrz) stara klasa, z której ją wywodzisz, jeśli nowa klasa ma być publicznie dostępna wszystkie funkcje, które posiadała stara klasa ... wtedy dziedziczysz.

Skład lub logika: jeśli nowa klasa musi tylko wewnętrznie używać starej klasy, bez ujawniania jej cech publicznych, wówczas używasz kompozycji - to znaczy, masz instancję starej klasy jako właściwość członka lub zmienna nowego. (Może to być własność prywatna, chroniona lub cokolwiek, w zależności od przypadku użycia). Kluczową kwestią jest to, że jest to przypadek użycia, w którym nie chcesz ujawniać oryginalnych funkcji klasowych i używać ich publicznie, wystarczy użyć ich wewnętrznie, podczas gdy w przypadku dziedziczenia udostępniasz je publicznie, poprzez nowa klasa. Czasami potrzebujesz jednego podejścia, a czasem drugiego.

Interfejsy: interfejsy mają jeszcze jeden przypadek użycia - gdy chcesz, aby nowa klasa częściowo, a nie całkowicie, wdrożyła i ujawniła publicznie funkcjonalność starej. Dzięki temu masz nową klasę, klasę z zupełnie innej hierarchii niż stara, zachowującą się jak stara tylko w niektórych aspektach.

Powiedzmy, że masz różne stworzenia reprezentowane przez klasy i mają one funkcje reprezentowane przez funkcje. Na przykład ptak miałby Talk (), Fly () i Poop (). Klasa Kaczka odziedziczyłaby po klasie Ptak, wdrażając wszystkie funkcje.

Class Fox oczywiście nie może latać. Jeśli więc zdefiniujesz przodka, który ma wszystkie funkcje, nie możesz poprawnie wyprowadzić potomka.

Jeśli jednak podzielisz funkcje na grupy reprezentujące każdą grupę wywołań za pomocą interfejsu, powiedzmy IFly, zawierającego Takeoff (), FlapWings () i Land (), możesz dla klasy Fox zaimplementować funkcje z ITalk i IPoop ale nie IFly. Następnie zdefiniowałbyś zmienne i parametry, aby zaakceptować obiekty, które implementują określony interfejs, a następnie kod współpracujący z nimi wiedziałby, co może wywołać ... i zawsze może zapytać o inne interfejsy, jeśli musi sprawdzić, czy inne funkcjonalności są również zaimplementowane dla bieżącego obiektu.

Każde z tych podejść ma przypadki użycia, gdy jest najlepsze, żadne podejście nie jest absolutnie najlepszym rozwiązaniem dla wszystkich przypadków.

Dragan Juric
źródło
1

W przypadku gry, zwłaszcza z Unity, która współpracuje z architekturą elementów składowych bytu, powinieneś preferować kompozycję zamiast dziedziczenia składników gry. Jest o wiele bardziej elastyczny i pozwala uniknąć sytuacji, w której chciałbyś, aby istota gry była „dwiema” rzeczami, które znajdują się na różnych liściach drzewa spadkowego.

Powiedzmy na przykład, że masz TalkingAI na jednej gałęzi drzewa spadkowego, a VolumetricCloud na innej osobnej gałęzi, a chcesz chmurę mówiącą. Jest to trudne w przypadku drzewa głębokiego dziedzictwa. Dzięki encji-komponentowi po prostu tworzysz encję, która ma komponenty TalkingAI i Cloud, i możesz zacząć.

Ale to nie znaczy, że powiedzmy w implementacji chmury wolumetrycznej, nie powinieneś używać dziedziczenia. Kod tego może być znaczny i składać się z kilku klas, i możesz użyć OOP w razie potrzeby. Ale wszystko będzie sprowadzało się do jednego komponentu gry.

Na marginesie, mam z tym problem:

Zwykle tworzę klasę podstawową (głównie abstrakcyjną) i używam dziedziczenia obiektów, aby udostępniać tę samą funkcjonalność różnym innym obiektom.

Dziedziczenie nie służy do ponownego użycia kodu. To dla ustanowienia is-a związek. Wiele razy idzie to w parze, ale musisz być ostrożny. Tylko dlatego, że dwa moduły mogą wymagać użycia tego samego kodu, nie oznacza to, że jeden jest tego samego typu co drugi.

Możesz użyć interfejsów, jeśli chcesz, powiedzmy, mieć w sobie List<IWeapon>różne rodzaje broni. W ten sposób możesz dziedziczyć zarówno interfejs IWeapon, jak i podklasę MonoBehaviour dla wszystkich rodzajów broni i uniknąć problemów z brakiem wielokrotnego dziedziczenia.

Sava B.
źródło
-3

Wygląda na to, że nie rozumiesz, czym jest interfejs. Zajęło mi ponad 10 lat myślenia o OO i zadawanie naprawdę mądrym ludziom pytań, czy mogłem mieć chwilę ah-ha z interfejsami. Mam nadzieję że to pomoże.

Najlepiej jest użyć ich kombinacji. W moim umyśle OO modelowanie świata i ludzi, powiedzmy, głęboki przykład. Dusza jest połączona z ciałem poprzez umysł. Umysł JEST interfejsem między duszą (niektórzy uważają intelekt) a poleceniami, jakie wydaje ciału. Funkcjonalny interfejs umysłu do ciała jest taki sam dla wszystkich ludzi.

Tę samą analogię można zastosować między osobą (ciałem i duszą) łączącą się ze światem. Ciało jest interfejsem między umysłem a światem. Każdy łączy się ze światem w ten sam sposób, jedyną wyjątkowością jest to, w jaki sposób wchodzimy w interakcję ze światem, czyli w jaki sposób używamy naszego UMYSŁU, aby DECYZOWAĆ, w jaki sposób łączymy się / współdziałamy w świecie.

Interfejs jest wtedy po prostu mechanizmem do powiązania twojej struktury OO z inną inną strukturą OO, przy czym każda strona ma różne atrybuty, które muszą negocjować / mapować względem siebie, aby wytworzyć funkcjonalny wynik w innym medium / środowisku.

Tak więc, aby umysł mógł wywołać funkcję otwartej ręki, musi zaimplementować interfejs nerwowy_komenda_system, KTÓRY ma swój własny interfejs API z instrukcjami, jak zaimplementować wszystkie wymagania niezbędne przed wywołaniem otwartej ręki.

Christopher Hoffman
źródło