Si può avere abstract / classi "vuoti"?

10

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

Sto facendo un clone di breakout e stavo facendo un progetto di classe. Volevo usare l'ereditarietà, anche se non dovevo, per applicare ciò che ho imparato in C ++. Stavo pensando al design della classe e ho trovato qualcosa di simile:

GameObject - > classe base (costituita da membri di dati come x e y offset 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 dati diverso da costruttore e distruttore (almeno per ora?).

In seguito stavo progettando di derivare una classe da NonMovableObject, come Tileset: NonMovableObject. Mi stavo chiedendo se sono spesso usate classi astratte "vuote" o solo classi vuote ... 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'utilizzo dell'ereditarietà e sulla progettazione di una sorta di framework di gioco.

    
posta ShrimpCrackers 06.04.2011 - 09:06
fonte

5 risposte

2

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

Ma, in quella situazione, suggerirei personalmente di ricordare l'assioma " preferisci la composizione (incapsulamento) sull'ereditarietà " e guarda invece il pattern di stato . Se vuoi che ogni oggetto abbia il suo stato di movimento in un secondo momento, passa a GameObject. Ad esempio: DiagonalBouncingMovementState per la palla, HoriazontalControlledMovementState per la pagaia, NoMovementState per un mattone.

1) Questo ti renderà più facile scrivere test unitari , se scegli di (quale, ancora una volta , Lo consiglierei) - perché puoi testare GameObject e ciascuno dei tuoi stati in modo indipendente.

2) Supponiamo che tu abbia dei token che scendono dai mattoni, ma poi vuoi cambiarli one in modo che si muovano in diagonale - piuttosto che spostarli attorno a una gerarchia complessa di ereditarietà di classe, puoi semplicemente cambia lo stato del movimento hardcoded che viene passato ad esso.

3) La cosa più importante: quando vuoi iniziare a cambiare nel gioco come la palla e / o la pagaia si muovono in base a quei segnalini, devi semplicemente cambiare il suo stato di movimento (DiagonalMovementState diventa DiagonalWithGravityMovementState).

A questo punto, nulla di ciò suggeriva che l'ereditarietà è sempre cattiva, solo per dimostrare perché preferiamo l'incapsulamento sull'ereditarietà. Potresti comunque voler derivare ogni tipo di oggetto da GameObject e far capire a quelle classi il loro stato iniziale di movimento. Quasi certamente vorrai derivare ciascuno dei tuoi stati di movimento da una classe MovementState astratta perché il C ++ non ha interfacce.

$ 0.02

    
risposta data 06.04.2011 - 09:58
fonte
8

In Java, le interfacce "vuote" sono usate come marcatori (ad esempio Serializable), perché in fase di runtime, gli oggetti possono essere controllati, sia che non "implementino" quell'interfaccia; ma in C ++, sembra piuttosto inutile per me.

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 . L'ereditarietà senza significato non rende un programma migliore.

    
risposta data 06.04.2011 - 09:25
fonte
3

Non ci pensi troppo; stai pensando e questo è buono.

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

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

Considera di scrivere astrazioni pure che definiscono il contratto dei tuoi oggetti. Il contratto dei tuoi oggetti è l'interfaccia rivolta verso l'esterno. La sua interfaccia pubblica. Che cosa fa.

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

Nel tuo progetto, hai una classe GameObject . Stai facendo affidamento su di esso per i suoi dati e non per la sua funzionalità. ritiene efficace e corretto utilizzare l'ereditarietà per condividere quelli che sembrano dati condivisi comuni, nel tuo caso x e y .

Questo non è l'uso corretto dell'ereditarietà. Ricorda sempre che l'ereditarietà è la forma più stretta di accoppiamento che puoi avere e dovresti sforzarti di ottenere un accoppiamento libero in ogni momento.

Per sua stessa natura NonMovableObject non si muove. Il x e y dovrebbero essere sicuramente dichiarati const . Per sua stessa natura MoveableObject deve spostarsi. Non può dichiarare x e y come const . Pertanto, questo non è lo stesso dato e non può essere condiviso.

Considera la funzionalità o il contratto dei tuoi oggetti. Cosa dovrebbero fare ? Ottieni quello giusto e i dati arriveranno. Lavora su un oggetto alla volta. Preoccupati di ciascuno a turno. Troverete che i vostri buoni disegni sono riutilizzabili.

Forse non c'è affatto NonMovableObject . Che dire di un'astrazione pura GameObjectBase che definisce un metodo move() ? Il tuo sistema installa GameObject s che implementa move() e il sistema sposta solo quelli che devono spostarsi.

Questo è solo l'inizio; un assaggio. La tana del coniglio va molto più in profondità. Non c'è un cucchiaio.

    
risposta data 09.09.2012 - 23:44
fonte
2

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

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

    
risposta data 06.04.2011 - 09:32
fonte
1

Una situazione in cui potresti vedere questo è dove desideri che una raccolta strongmente tipizzata contenga tipi altrimenti eterogenei. Non dico che sia una grande idea, potrebbe indicare un'astrazione debole o un design scadente, ma succede. Come dici tu, è un modo di categorizzare le cose e può essere utile e perfettamente valido.

es. Vuoi una collezione per ospitare Gatti e Cani. Nel tuo progetto decidi che hanno molto in comune, quindi disponi di una classe base di Mamal e usa questo tipo nella tua raccolta (ad es. Elenco). Tutto bene. Poi decidi di aggiungere delle rane alla tua collezione ma non sono dei mamali, quindi un modo per farlo sarebbe quello di creare sottoclassi di Animal e Mammiferi, ma forse non riesci a pensare molto alle Rane e ai Mammiferi comune quindi Animal rimane vuoto. Poi scrivi la tua collezione con Animal invece di Mamal e tutto va bene.

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

    
risposta data 06.04.2011 - 10:22
fonte

Leggi altre domande sui tag