Puoi avere abstract / classi "vuote"?


10

Certo che puoi, mi chiedo solo se è razionale progettare in questo modo.

Sto creando un clone di successo e stavo facendo un po 'di design di classe. Volevo usare l'ereditarietà, anche se non è necessario, per applicare ciò che ho imparato in C ++. Stavo pensando al design di classe e ho pensato a qualcosa del genere:

GameObject -> classe base (è composta da membri di dati come offset xey, e un vettore di SDL_Surface *

MovableObject: GameObject -> classe astratta + classe derivata di GameObject (un metodo void move () = 0;)

NonMovableObject: GameObject -> classe vuota ... nessun metodo o membro di dati diverso da costruttore e distruttore (almeno per ora?).

Più tardi stavo pianificando di derivare una classe da NonMovableObject, come Tileset: NonMovableObject. Mi stavo solo chiedendo se le classi astratte "vuote" o solo le classi vuote sono spesso usate ... Ho notato che nel modo in cui sto facendo questo, sto solo creando la classe NonMovableObject solo per motivi di categorizzazione.

So che sto pensando troppo alle cose solo per creare un clone di breakout, ma il mio focus è meno sul gioco e più sull'uso dell'eredità e sulla progettazione di una sorta di framework di gioco.

Risposte:


2

In C ++ hai l'ereditarietà multipla, quindi non c'è letteralmente alcun vantaggio nell'avere quelle classi vuote. Se vuoi che Ball erediti da GameObject e MovableObject in un secondo momento (ad esempio, vuoi tenere un array di GameObjects e chiamare un metodo Tick ogni nth di secondo per farli muovere tutti), è abbastanza facile da fare.

Ma, in quella situazione, suggerirei personalmente di ricordare invece l'assioma " preferire la composizione (incapsulamento) piuttosto che l' eredità " e guardare il modello di Stato . Se vuoi che ogni oggetto abbia il suo stato di movimento in un secondo momento, passalo a GameObject. Ad esempio: DiagonalBouncingMovementState per la palla, HoriazontalControlledMovementState per la paletta, NoMovementState per un mattone.

1) Ciò renderà più semplice la scrittura di unit test , se lo si sceglie (che, ancora una volta, lo consiglierei) - perché è possibile testare GameObject e ciascuno dei propri stati in modo indipendente.

2) Supponi di avere gettoni che cadono dai mattoni ma poi vuoi cambiarne uno in modo che si muovano in diagonale - piuttosto che spostarlo attorno a una complessa gerarchia di eredità di classe, puoi semplicemente cambiare lo stato di movimento codificato che gli viene passato .

3) Soprattutto: quando vuoi iniziare a cambiare nel gioco il modo in cui la palla e / o la paletta si muovono in base a quei token, continui a cambiare il suo stato di movimento (DiagonalMovementState diventa DiagonalWithGravityMovementState).

Ora, nulla di tutto ciò doveva suggerire che l'eredità sia sempre negativa, solo per dimostrare perché preferiamo l'incapsulamento rispetto all'eredità. Potresti comunque voler derivare ogni tipo di oggetto da GameObject e far capire a quelle classi il loro stato iniziale di movimento. Quasi sicuramente vorrai derivare ciascuno dei tuoi stati di movimento da una classe astratta di MovementState perché C ++ non ha interfacce.

$ 0.02


8

In Java, le interfacce "vuote" vengono utilizzate come marker (ad es. Serializzabili), poiché in fase di esecuzione gli oggetti possono essere controllati indipendentemente dal fatto che "implementino" tale interfaccia; ma in C ++, mi sembra abbastanza inutile.

IMO, le funzionalità linguistiche dovrebbero essere considerate strumenti, qualcosa che ti aiuta a raggiungere determinati obiettivi e non obblighi. Solo perché puoi usare una funzione non significa che devi farlo . L'eredità insignificante non migliora un programma.


3

Non ci stai pensando troppo; stai pensando e va bene.

Come già detto, non utilizzare una funzione di lingua solo perché è lì. I compromessi delle funzionalità di apprendimento della lingua sono fondamentali per un buon design.

Non si dovrebbero usare le classi di base per la categorizzazione. Ciò potrebbe implicare il controllo del tipo. Questa è una cattiva idea.

Prendi in considerazione la scrittura di astrazioni pure che definiscono il contratto dei tuoi oggetti. Il contratto dei tuoi oggetti è l'interfaccia rivolta verso l'esterno. La sua interfaccia pubblica. Cosa fa.

Nota: è importante capire che questo non è quali dati ha x, yma cosa fa move().

Nel tuo design, hai una lezione GameObject. Ti stai affidando per i suoi dati e non per la sua funzionalità. Si sente efficiente e corretto utilizzare l'ereditarietà per la condivisione di quello che sembra dati comuni condivisi, nel tuo caso xe y.

Questo non è l'uso corretto dell'eredità. Ricorda sempre che l'ereditarietà è la forma più stretta di accoppiamento che puoi avere e dovresti sempre cercare un accoppiamento lento.

Per sua stessa natura NonMovableObjectnon si muove. È xe ydovrebbe certamente essere dichiarato const. Per sua stessa natura ha MoveableObjectbisogno di muoversi. Non può dichiarare il suo xe ycome const. Pertanto, questi non sono gli stessi dati e non possono essere condivisi.

Considera la funzionalità o il contratto dei tuoi oggetti. Cosa dovrebbero fare ? Capisci bene e i dati arriveranno. Lavora su un oggetto alla volta. Preoccupati per ciascuno a sua volta. Scoprirai che i tuoi buoni disegni sono riutilizzabili.

Forse non c'è NonMovableObjectaffatto. Che dire di una pura astrazione GameObjectBaseche definisce un move()metodo? Il tuo sistema stabilisce GameObjectquale attuare move()e sposta solo quelli che devono muoversi.

Questo è solo l'inizio; un assaggio. La tana del coniglio è molto più profonda. Non c'è il cucchiaio.


Mentre è vero che né MovableObjectNonMovableObjectpossono ereditare l'uno dall'altro, entrambi hanno una posizione; come tale, entrambi dovrebbero essere consumabili da un codice che, ad esempio, vuole indirizzare l'obiettivo di un mostro verso un oggetto senza considerare se tale oggetto possa muoversi o meno.
supercat

2

Ha applicazioni in C # come ammoQ menzionate, ma in C ++ l'unica applicazione sarebbe se volessi avere un elenco di puntatori NonMovableObject.

Ma poi immagino che dovresti distruggere gli oggetti attraverso il puntatore NonMoveableObject, nel qual caso avresti bisogno di distruttori virtuali e non sarebbe più una classe vuota. :)


Ho pensato anche a quel caso, ma cosa potresti fare con un simile elenco di oggetti che non espongono alcun comportamento? Molto probabilmente, potresti scoprire in qualche modo il tipo reale e utilizzare un cast per accedere ai metodi e ai campi disponibili - sicuramente un odore di codice.
user281377

Sì, questo è un buon punto.
tenpn

1

Una situazione in cui potresti vedere questo è dove vuoi che una raccolta fortemente legata contenga tipi altrimenti eterogenei. Non sto dicendo che è un'ottima idea, può indicare un'astrazione debole o un design scadente, ma succede. Come hai detto, è un modo di classificare le cose e può essere utile e perfettamente valido.

Ad esempio, vuoi che una collezione contenga Cats and Dogs. Nel tuo progetto decidi che hanno molto in comune, quindi hai una classe base di Mamal e usa questo tipo nella tua collezione (es. Elenco). Tutto bene. Quindi decidi di voler aggiungere alcune Rane alla tua collezione ma non sono mamali, quindi un modo per farlo sarebbe quello di rendere la sottoclasse Frogs e Mamals di Animali, ma forse non riesci a pensare a molto delle Rane e dei Mamali comune quindi l'animale rimane vuoto. Quindi digiti la tua raccolta con Animal anziché Mamal e tutto va bene.

Nella vita reale trovo che, anche se prima o poi creo una classe base vuota, alcune funzionalità finiscono lì dentro, ma ci sono certamente momenti in cui esistono.


Una domanda da porsi, tuttavia, è: se i tipi non hanno nulla in comune, perché vengono tenuti nella stessa collezione? Non è possibile eseguire alcuna operazione su tutti gli elementi della raccolta, il che in qualche modo sconfigge lo scopo di avere una tale raccolta. Ora potrebbero esserci alcune operazioni artificiali che potresti voler aggiungere a entrambe per semplificare la logica - ad esempio l'implementazione del modello di progettazione dei Visitatori - a quel punto potrebbe tornare utile, ma ora hanno qualcosa in comune ...
Jules
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.