Prassi di ereditarietà?

2

Sono abbastanza nuovo nella programmazione orientata agli oggetti e ho una domanda su cui mi sono imbattuto riguardo alle migliori pratiche di ereditarietà.

Sto costruendo un sistema per le armi in un gioco. Ho una classe base chiamata Weapon e due sottoclassi - Melee e Projectile (come coltelli e pistole) - che ereditano da Weapon .

Tutte le armi hanno statistiche (come Attack Point) e parti (come Grip, Magazine e Blade). Le pistole e i coltelli della cabina hanno attacco e impugnatura. Ma solo le pistole hanno riviste e solo i coltelli hanno lama.

La mia domanda qui è come pensare quando si eredita da Weapon :

  1. Tematicamente un'arma consiste di parti quindi sarebbe logico avere le classi Melee e Projectile come responsabili di tutte le parti di cui è composta l'arma, quindi Melee ha un grip e una lama e Projectile ha una presa e una rivista. Ma poi vorrei duplicare il campo Grip.

  2. Poiché entrambi i tipi di arma hanno Grip, Grip dovrebbe essere una parte della classe base Weapon ? Sembra strano estrapolare Grip e metterlo da solo in un'altra classe.

  3. Weapon contiene tutte le parti, anche se le sottoclassi non le usano tutte? Ma sembra che aumenterò la classe Weapon .

Esiste una sorta di best practice? Grazie!

    
posta pend 08.06.2018 - 14:20
fonte

4 risposte

6

L'ereditarietà (spinta all'estremo) non è un approccio efficiente nel modellare qualcosa come armi o nemici in un gioco. È abbastanza comune vedere le persone che pensano di avere Enemy , FlyingEnemy , GroundEnemy , quindi voler aggiungere altro comportamento e realizzare che finirebbero con InvisibleFlyingEnemy e così via.

Stai provando a scomporre l'arma nelle parti secondarie, cercando quindi di evitare la duplicazione. Potresti chiederti perché hai diverse classi per le armi, ma la tua classe Grip è condivisa da entrambi (vale a dire perché non BladeGrip e ProjectileGrip ). Questo può facilmente causare strani alberi ereditari e alla fine nessuno di loro ti rende davvero comodo.

Dato che non funziona molto bene, generalmente nello sviluppo del gioco si evita l'ereditarietà e si usa Entity-Component-System architecture.

Non sto dicendo che non dovresti avere nessuna eredità, ma non c'è alcun vantaggio nel cercare di ottenere una mappatura 1-a-1 tra le classi e ciò che vedi nel gioco. Gli svantaggi includono la rigidità grave quando si tenta di aggiungere nuove cose e di sprecare tempo a lavorare sulla gerarchia di ereditarietà invisibile anziché sul gioco visibile.

    
risposta data 08.06.2018 - 14:44
fonte
1

In una situazione come questa quando l'ereditarietà non si adatta abbastanza, è probabilmente perché questi attributi come grip, attacco, rivista, lama non sono ciò che definisce l'arma. Pertanto non hai a che fare con un "WeaponWithGrip" o un "WeaponWithMelee", perché è possibile che tu possa mescolare e abbinare. Dal momento che le tue classi non possono (o almeno non dovrebbero) avere riferimenti di tipo diamante (che ereditano da più classi), dovresti ripensare a cosa significhi per un'arma avere questi attributi.

Considera ciascuno di questi aspetti e decidi cosa definisce una pistola e quale è semplicemente un attributo di quella pistola. Potresti dire che un aspetto definisce una pistola quando il suo comportamento deve cambiare completamente.

Supponi di avere una classe Weapon . Questa arma ha un membro privato MeleeExtension che potrebbe essere assente. Nel tuo programma, devi infliggere danno da mischia con la tua istanza Weapon attuale. Controlla se il tuo MeleeExtension è assente. Se lo è, la tua arma non può essere utilizzata per infliggere danni in mischia (nessuna lama o altra estensione a distanza ravvicinata). Se esiste, chiedi al tuo MeleeExtension la quantità di danni inflitti. Senza sapere che tipo di arma è, la tua classe di Armi può occuparsi degli incontri in mischia. MeleeExtension potrebbe quindi essere implementato da Blade , Knife o qualsiasi altra cosa.

Lo stesso si può dire per Grip o tipo di proiettile sparato, ecc. Ora supponiamo per un secondo che per il funzionamento dell'arma del tuo stand, devi essere in modalità di fuoco. Questo non è qualcosa che può essere semplicemente corretto facendo una sorta di FiringModeExtension . Questo è un tipo di arma completamente nuovo, quindi dovresti trattarlo come tale creando un BoothWeapon che dice, ti permette di sparare solo se il tiratore è in modalità di fuoco.

Se un'estensione non ha senso su Weapon , è perché appartiene a un nuovo tipo di arma (come ad esempio BoothWeapon ). Weapon non dovrebbe mai avere bisogno di sapere dell'esistenza di tali estensioni per funzionare correttamente (se lo fa, allora il tuo ragionamento non è corretto in un senso o nell'altro).

    
risposta data 08.06.2018 - 14:45
fonte
1
  1. Se la classe Weapon ha un grip e le classi Melee e Projectile ereditano da Weapon , continuano a "essere responsabili" (nelle tue parole) di quel grip. L'ereditarietà consente alle sottoclassi di utilizzare un campo della loro sottoclasse come se fosse stato dichiarato nella sottoclasse stessa (supponendo che il campo sia visibile).

  2. Preferisco vederlo come una rimozione delle funzionalità duplicate da Melee e Projectile rispetto alla rottura delle classi. È vero che l'uso dell'ereditari risulterà nell'avere alcune funzionalità nella superclasse, ma ciò non è necessariamente negativo.

  3. Una classe dovrebbe fare riferimento solo ai tipi che effettivamente utilizza. Pertanto la classe Weapon non dovrebbe avere una rivista e un blade.

L'utilizzo dell'ereditarietà non è sempre la scelta migliore. Ma il tuo esempio potrebbe essere una buona idea perché ti permette di separare chiaramente tra i due tipi di armi. Soprattutto se sei nuovo nella programmazione orientata agli oggetti e vuoi conoscere l'ereditarietà.

Esistono diversi articoli su quando utilizzare l'ereditarietà rispetto alla composizione, ad esempio questo o questo . Un approccio semplice consiste nel decidere se class A è un class B o class A ha class B . Nel tuo caso, possiamo vedere che un'arma da mischia è un'arma e un'arma proiettile è un'arma. Questo indica l'uso dell'ereditarietà.

Ma se l'albero dell'ereditarietà diventa più grande, può essere molto confuso. Quindi forse prova a pensare a quale portata avrà il tuo modello. Ci saranno altri tipi di armi? Ci sono delle eccezioni dalla tua regola di grip? È persino necessario modellare la presa, la rivista o la lama come classe? Se sì, quali funzionalità portano queste classi?

    
risposta data 08.06.2018 - 14:35
fonte
1

Ci sono 2 modi principali per costruire funzionalità:

  • Composizione : potenzia le tue funzionalità con un insieme di istanze di oggetti che funzionano insieme ("ha una" relazione)
  • Eredità : modifica del comportamento di qualcosa che utilizza la stessa struttura dell'oggetto (la relazione "è una")

Sembra che concetti come riviste, tipo di proiettile, barile, ecc. forniscano dei modificatori alla pistola base. Allo stesso modo ci possono essere altri modificatori per le armi da mischia.

Quando progetti le tue classi devi pormi alcune domande per assicurarti di non pensare troppo:

  • La composizione di un'arma è più che decorativa? In altre parole, è l'unica vera differenza del testo del gusto, o intendi avere parti che puoi scambiare?
  • Ci sono differenze nel modo in cui calcoli danno, colpi critici o possibilità di colpire?

Ci sono più domande, ma non hanno alcun impatto sulla decisione tra composizione ed eredità. Impattano solo su quali attributi devi tenere traccia.

Diamo un'occhiata al caso semplice:

  • C'è un solo algoritmo per tutto e un set standard di attributi. Le armi possono essere sostituite solo come un'entità intera e non hanno parti sostituibili.

In questo caso non c'è bisogno di composizione o eredità. Devi solo avere un gruppo di oggetti con gli attributi corretti impostati.

Caso leggermente più complicato:

  • Sono disponibili due set di algoritmi: uno per il corpo a corpo e uno per le armi a proiettile
  • All'interno di ogni set, ci sono alcuni attributi comuni, ma anche alcuni attributi specializzati inclusi nel calcolo

In questo caso starai guardando all'ereditarietà:

  • Arma : attributi comuni e definizione dei nomi delle funzioni (calculateDamage, calculateCriticalHit, didHit)
  • MeleeWeapon : attributi speciali per le armi da mischia e implementa le funzioni definite nella classe base
  • ProjectileWeapon : attributi e metodi speciali per le armi a proiettile (come il conteggio delle munizioni, il ricaricamento, ecc.) e implementa le funzioni definite nella classe base

Questo ti permette di interagire con le armi in modo generico quando effettivamente li usi, ma gli oggetti reali gestiranno tutte le differenze nei calcoli.

Infine, il più complesso sarebbe avere parti sostituibili. Per questi avresti la funzionalità composta da più oggetti che lavorano insieme. I tuoi attributi di classe avrebbero istanze di quelle classi.

    
risposta data 08.06.2018 - 15:56
fonte

Leggi altre domande sui tag