C'è qualche danno nell'avere classi costituite per lo più da classi ereditate?

4

Dire che ho alcune classi base:

  • HasPosition fornisce una posizione 2D e metodi per "spostare".
  • IsDisplayable definisce il modo in cui una classe verrà visualizzata in una determinata libreria grafica (ad esempio, curses) e i metodi per modificarne la rappresentazione.
  • HasHealth fornisce un valore di integrità e metodi hurt e heal .

Se multiplo-eredito tutti i 3 in una singola classe, Entity , avrò una classe che può essere visualizzata, spostata e ferita (un punto di partenza per un'entità di gioco).

Questo potrebbe essere aggiunto a e mescolato per produrre un numero qualsiasi di classi.

C'è qualcosa di sbagliato in questo approccio? È normale avere classi "di primo livello" più o meno composte da diverse altre classi base?

Sto provando a creare un set riutilizzabile da usare per creare giochi usando Pdcurses, e mi chiedevo se questo fosse un buon modo per impostare le mie classi.

    
posta Carcigenicate 16.04.2015 - 05:43
fonte

2 risposte

3

Is there anything wrong with this approach? Is it at all typical to have "top level" classes that are more or less made up of several other base classes?

Sì, e sì.

Storicamente, è stata pratica comune tra gli sviluppatori di motori di gioco di costruire le loro entità di gioco esattamente nel modo in cui descrivete. Ho, infatti, diversi libri ben noti che descrivono un modello di ereditarietà per le entità di gioco.

Tuttavia, negli ultimi due decenni, gli sviluppatori hanno osservato che l'ereditarietà è intrinsecamente limitante:

  • Modifica della logica di gioco per aggiungere nuove funzionalità richiede modificare il motore di gioco.
  • Le relazioni tra entità devono essere hard-coded.
  • I dati delle entità sono, per necessità, distribuiti su vaste aree di memoria, incorre in penalità di grandi prestazioni per cicli di aggiornamento per sistema ristretti. (Ad esempio, mentre si fa la fisica su insiemi di oggetti.)

Invece, ciò che è diventato di moda tra gli sviluppatori di giochi (e per una buona ragione) è sistemi di entità basati sui dati.

In un sistema entity- componente , ci sono molti vantaggi in termini di prestazioni ed estensibilità:

  • La modifica della logica di gioco può essere fatta banalmente collegando un nuovo componente a un'entità.
  • I componenti delle entità possono essere allocati in lotti, per mantenere vicini i dati simili; questo consente, ad esempio, a un sistema fisico di eseguire rapidamente i calcoli sui dati che letteralmente si riversano nella cache della CPU, con grandi vantaggi in termini di prestazioni.
  • Le relazioni tra entità possono essere definite in fase di esecuzione, in modo che le entità possano essere costruite interamente all'esterno del motore, in un motore di scripting o anche in rete.
  • Ristrutturazione più rapida delle nuove funzionalità: i nuovi componenti e i sistemi di componenti toccano un numero inferiore di parti della base di codice, richiedendo meno lavoro a lungo termine.

Il design basato sui dati come questo ei sistemi di componenti di entità in generale sono un argomento frequente su gamedev.stackexchange.com .

    
risposta data 16.04.2015 - 07:03
fonte
3

Consentitemi di fornire una risposta diversa, ma tipica: dipende.

Molte persone (me incluse) hanno problemi con gerarchie di ereditarietà come quelle di @greyfade menzionate nell'altra risposta. Il mantra del codice pulito "preferisce la composizione sull'ereditarietà" deriva esattamente da questo.

Tuttavia , comprendendo i problemi che sono alla base delle gerarchie di ereditarietà, puoi ancora ottenere il meglio da entrambi i mondi. Tenete presente, tuttavia, che questo dipende in gran parte dalla vostra lingua e dalla vostra autodisciplina.

Quello che hai descritto è qualcosa che semplicemente non era possibile in un linguaggio come Java (precedente a Java 8, che ora ha le interfacce predefinite), ma accettato prontamente in C ++ prima e in linguaggi più moderni come C # o Scala. Per salvare le noiose discussioni originate dai problemi sopra menzionati, tuttavia, hanno scelto di chiamare questi "mixin" o "tratti" (in opposizione a "multiple" "eredità" - due parole che sono prontamente attaccabili da tutti con un livello CS di base). In sostanza, la tua descrizione sembra proprio come la stai facendo.

Ciò che è importante in un approccio basato sui tratti, proprio come in uno basato sulla composizione (anziché sull'eredità), è quello di concentrarsi su due vantaggi principali:

  1. Vuoi essere in grado di riutilizzare una di queste "cose" senza doversi preoccupare degli altri

  2. e vuoi poter cambiare o sostituire uno di questi, sempre senza preoccuparti troppo degli altri.

Chiaramente, quando si ha una gerarchia completa di cose in un grande albero, è quasi impossibile ottenere questi due vantaggi per una classe di implementazione che si trova da qualche parte nel mezzo di quell'albero. Tuttavia, quando lavori con tratti / mixin, o essenzialmente fai la stessa cosa attraverso l'autodisciplina in C ++ in quanto erediti più volte, ma non forma una gerarchia che ha una profondità maggiore di un singolo passo di ereditarietà, allora hai tecnicamente usato l'ereditarietà, eppure puoi mantenere tutti questi vantaggi.

Puoi riutilizzare ciascuna delle singole parti in modo indipendente, o persino scambiarle con un lavoro ragionevolmente piccolo. Eppure questo sistema non ti salva di creare potenzialmente gerarchie di classi intangibili di nuovo (ma poi di nuovo, neanche un martello ti risparmia di colpire la testa con esso).

di responsabilità; Per quanto riguarda i sistemi di entità, personalmente non sono convinto della loro superiorità rispetto ai tratti. Entrambi sono molto meglio di una grande gerarchia di ereditarietà. Inoltre non sono nel settore dei giochi, quindi altri effetti (come i citati accessi veloci alla memoria) possono anche essere abbastanza importanti per il tuo caso d'uso per giustificare la scelta di un modo rispetto all'altro. Come al solito, non c'è un proiettile d'argento (a meno che non ne crei uno nel gioco) e tutto ha i suoi pro e contro. Assicurati solo di comprendere gli aspetti negativi di qualsiasi scelta.

In risposta alle domande del commento aggiuntivo:

Non è che i diversi approcci differiscano dall'essere adatti o meno per un semplice gioco. Non credo che tu abbia problemi di scalabilità con nessuno dei due. I tratti / Mixin sono usati con successo in sistemi piuttosto grandi e si adattano bene, specialmente perché sono così piacevolmente componibili. Non ho idea di come sia venuta la scalabilità, ma non è davvero un problema per nessuno dei due approcci, per quanto posso dire.

Affrontare il tuo commento, per quanto ho capito, basato sui dati non significa che tu abbia una composizione standard tramite un attributo nella tua classe. Pertanto la tua classe Entity non fa riferimento direttamente a una classe Position , poiché ciò comporterebbe di nuovo un accoppiamento più elevato del previsto. Per aggiungere un "componente" a un'entità, dovresti comunque andare in quella classe. Per quanto ne so, e per favore sii consapevole che non sono esperto su questo, l'idea è di scindere la tua Entità dal dover conoscere la cosa concreta "componente", ma invece fare affidamento su una rappresentazione intermedia. Un esempio molto semplificato può essere che l'entità è un archivio di valori-chiave e il componente Salute genera una voce "health" -> 10 in quell'archivio. In questo modo, il mondo esterno può aggiungere componenti all'entità, senza che l'entità stessa debba sapere nulla su questi componenti.

Non sono sicuro di aver compreso correttamente l'idea dei guru dei giochi (le correzioni di qualcuno che conosce meglio sono sempre benvenute), ma ho l'impressione che con questo approccio sacrifichi molta sicurezza e ragionamento in fase di compilazione abilità come compromesso per ottenere un accoppiamento molto basso (da qui le buone opzioni di scripting con Lua e simili) e possibilità di implementazione potenzialmente più veloci (tramite allineamenti di memoria di questo componente per ottimizzare gli accessi alla memoria / alla cache). Supponendo che sia corretto, non potrei ancora rivendicare un modo per essere quello giusto. Tutti noi abbiamo le nostre preferenze e requisiti esterni che dovrebbero essere soddisfatti nel miglior modo possibile.

    
risposta data 16.04.2015 - 07:38
fonte

Leggi altre domande sui tag