La gerarchia delle classi OOP è troppo profonda?

4

Quindi sto creando un clone di Super Mario Bros NES in pygame e per tutti i nemici che sono attratti dalla finestra, ho una gerarchia di classi profonda 5 classi. Andando nell'ordine di:

  1. Oggetto (una classe base per tutte le cose disegnate sullo schermo)
  2. Personaggio (una classe per qualsiasi tipo di personaggio, giocabile o meno)
  3. Nemico (una classe per qualsiasi nemico nel gioco)
  4. BasicEnemy (Una classe per ogni nemico che si muove solo orizzontalmente, poiché entrambi agiscono allo stesso modo, solo visuali diverse, inclusi Goomba e Koopa).

Quindi le classi più profonde sono Goomba e Koopa, con la struttura di classe sopra indicata. Lo sto facendo in questo modo perché tutti gli oggetti disegnati sullo schermo hanno proprietà comuni, così come tutti i personaggi, i nemici e i nemici di base. Per esempio, tutti i nemici si muovono orizzontalmente attraverso lo stesso identico metodo (aggiungendo la loro velocità x alla loro posizione x). Ho letto in molti posti che troppa eredità è cattiva. Non sono sicuro se quello che sto facendo sia troppo. E se è troppo, come dovrei farlo?

    
posta Robbie 03.08.2015 - 23:14
fonte

3 risposte

8

Vorrei che i tuoi oggetti implementassero interfacce come ICharacter, IEnemy, IHorizontalMover. La linea guida di progettazione pertinente è conosciuta come Favorisci la composizione sull'ereditarietà e dovrebbe consentire al tuo design di essere più flessibile. Una differenza è che le interfacce tendono a specificare il comportamento e qualsiasi oggetto dato può implementare il massimo o il poco necessario.

Se ritieni che ciò provochi la duplicazione del codice che non avevi quando una classe base gestiva solo metodi non sovrascritti, dovresti utilizzare i metodi di inoltro, dove le classi di implementazione inoltrano le chiamate di metodo a una classe privata (o stateless classe statica) che fornisce una funzione comune a diverse implementazioni. Ciò mantiene la base di codici DRY (che significa Non ripetersi). Può sembrare che tu stia scrivendo più righe standard per ottenere ciò che l'ereditarietà si prende cura di te dietro le quinte, ma in genere è considerato utile, lasciando la base di codici più flessibile e aperta al cambiamento rispetto a un progetto di ereditarietà pura.

    
risposta data 03.08.2015 - 23:47
fonte
6

A prima vista sembra ragionevole utilizzare l'ereditarietà per separare diversi tipi di entità: questo è l'approccio "reale" a OOAD che la maggior parte delle università insegna in CS101. In altre parole, le classi sono denominate in base a oggetti del mondo reale che possiamo vedere: scrivania, cassettiera, porta, ecc.

Tuttavia, ciò ha poco senso nel contesto di un videogioco. Ci sono due categorie di preoccupazioni per gli oggetti che stai modellando:

  • Gli oggetti appaiono sullo schermo e devono essere disegnati, quindi ha senso modellarli usando le classi. Dopotutto hanno entrambe le proprietà (uno sprite o un modello 3D) e un comportamento (ad es. Movimento) che li rendono candidati per le classi.

  • Gli oggetti interagiscono. Un oggetto giocatore potrebbe interagire con un blocco di mattoni (dopo tutto questo è un clone di Mario) stando su di esso o distruggendolo. L'oggetto potrebbe interagire con un nemico calpestandolo.

Quando analizzo queste potenziali classi, vedo molte delle stesse preoccupazioni. Alcuni oggetti si muovono, altri no. Alcuni hanno "intelligenza" dietro di loro (AI o umano), altri no. Non vedo molto valore nella gerarchia che hai presentato: le preoccupazioni sono sparse troppo.

Raccomando di spostare le preoccupazioni su controllo oggetto al di fuori degli oggetti stessi. Ecco come lo progetterei, basato liberamente su MVC.

Attore sostituisce l'oggetto nella tua gerarchia. L'idea è che un attore abbia un qualche ruolo nel campo del gioco. Quel ruolo potrebbe essere "non fare nulla" nel caso di un mattone che non è smontabile e non esegue alcuna azione. Potrebbe essere in grado di rimuovere se stesso dal campo di gioco (il mattone viene distrutto). Potrebbe muoversi: potrebbe essere una piattaforma che pattuglia su un loop, un nemico con un'IA o un giocatore che riceve input da un dispositivo hardware.

ActorView viene passato a un attore e controlla come viene visualizzato. Questo potrebbe essere semplice come "Mostra sempre questo sprite" o "visualizza questo modello 3D in base a determinati criteri".

ActorController è il "cervello" dell'Actor. Si potrebbe avere un'implementazione "non fare nulla" che non cambia mai di stato (ad esempio un mattone infrangibile), un'implementazione semplice che può rimuovere l'attore (ad esempio un mattone fragile), un'implementazione più complessa (ad esempio una piattaforma che pattuglia avanti e indietro), un'implementazione AI (ad es. un nemico) o un giocatore (usa l'input hardware).

Ora puoi usare la composizione: l'attore dovrebbe davvero essere solo una classe che prende visione e un controller. ActorView potrebbe avere un piccolo numero di sottoclassi che possono essere riutilizzate. Hai davvero bisogno di una vista diversa per ogni mattone statico? No, puoi semplicemente riutilizzare una classe e passare una bitmap diversa ogni volta. ActorController potrebbe avere un piccolo numero di sottoclassi per ogni tipo di attore: statico, con script (mosse, pause), AI, giocatore.

    
risposta data 03.08.2015 - 23:49
fonte
1

Invece di cercare di capire la gerarchia di classi esatta di cui hai bisogno, che ne dici di lasciarla crescere organicamente?

Il grande vantaggio dell'ereditarietà è evitare la ripetizione. Quando inserisci funzionalità in una classe base, puoi condividere tale funzionalità tra molte classi diverse senza dover inserire lo stesso codice in ciascuna di esse.

Quindi ereditate da Object, scrivete il vostro codice e quindi iniziate a creare classi intermedie dove ha senso evitare la ripetizione. Quando trovi due oggetti che fanno la stessa cosa, crea una classe astratta tra loro e Object e tira il comportamento comune in quella classe astratta.

Se la tua gerarchia inizia a diventare troppo complessa, con un comportamento comune tra oggetti che non sono strettamente correlati, è allora che puoi iniziare a tirare il comportamento in oggetti separati e usare la composizione per ottenere quel comportamento ... di nuovo, in per evitare la ripetizione.

    
risposta data 03.08.2015 - 23:52
fonte

Leggi altre domande sui tag