2009-12-06 9 views
5

Sto riscontrando un problema nella progettazione OO in cui trovo il codice duplicato in 2 classi diverse. Ecco cosa sta succedendo:Progettazione OO e metodi speculari/duplicati

In questo esempio, voglio rilevare la collisione tra oggetti di gioco.

Ho un CollisionObject di base che contiene metodi comuni (come checkForCollisionWith) e CollisionObjectBox, CollisionObjectCircle, CollisionObjectPolygon che estendono la classe base.

Questa parte del design sembra ok, ma qui è quello che mi è preoccupante: chiamando

aCircle checkForCollisionWith: aBox 

eseguirà un cerchio vs Casella di controllo di collisione all'interno Circle sottoclasse. Al contrario,

aBox checkForCollisionWith: aCircle 

eseguirà il controllo della collisione tra caselle e cerchi all'interno della sottoclasse della casella.

Il problema è che il codice di collisione di Circle vs Box è duplicato, poiché è in entrambe le classi Box e Circle. C'è un modo per evitarlo o mi sto avvicinando a questo problema nel modo sbagliato? Per ora, mi sto propendo per avere una classe helper con tutto il codice duplicato e chiamarla dagli oggetti aCircle e aBox per evitare duplicati. Sono curioso di sapere se c'è una soluzione OO più elegante a questo, però.

risposta

1

Ho avuto lo stesso problema (lavorando nell'obiettivo C) e una soluzione che ho trovato per questo è definire una funzione esterna per risolvere la collisione quando conosco già i tipi per entrambi gli oggetti.

Per esempio, se ho Rettangolo e Cerchio, sia l'attuazione di un protocollo (tipo di interfaccia per questa lingua) Forma ..

@protocol Shape 

-(BOOL) intersects:(id<Shape>) anotherShape; 
-(BOOL) intersectsWithCircle:(Circle*) aCircle; 
-(BOOL) intersectsWithRectangle:(Rectangle*) aRectangle; 

@end 

definire intersectsWithCircle per Rettangolo e intersectsWithRectangle per Circle come questo

-(BOOL) intersectsWithCircle:(Circle*) aCircle 
{ 
    return CircleAndRectangleCollision(aCircle, self); 
} 

e ...

-(BOOL) intersectsWithRectangle:(Rectangle*) aRectangle 
{ 
    return CircleAndRectangleCollision(self, aRectangle); 
} 

Naturalmente non attacca il problema di accoppiamento di Double Dispatch, ma almeno evita la duplicazione del codice

0

Forse potresti avere un oggetto collision che contiene i metodi per testare diversi tipi di collisioni. I metodi potrebbero restituire altri oggetti che contengono il punto di collisione e altre informazioni necessarie.

3

Quello che si desidera è indicato come multi dispatch.

spedizione multipla o multimethods è la caratteristica di alcuni linguaggi di programmazione orientati agli oggetti in cui una funzione o metodo può essere dinamicamente spediti basati sul tempo di esecuzione tipo (dinamica) di più di uno dei suoi argomenti.

Questo può essere emulato nelle lingue OOP mainline, oppure è possibile utilizzare direttamente se si utilizza Common Lisp.

L'esempio Java nell'articolo di Wikipedia riguarda anche il problema esatto, il rilevamento delle collisioni.

Ecco il falso nelle nostre lingue "moderni":

abstract class CollisionObject { 
    public abstract Collision CheckForCollisionWith(CollisionObject other); 
} 

class Box : CollisionObject { 
    public override Collision CheckForCollisionWith(CollisionObject other) { 
     if (other is Sphere) { 
      return Collision.BetweenBoxSphere(this, (Sphere)other); 
     } 
    } 
} 

class Sphere : CollisionObject { 
    public override Collision CheckForCollisionWith(CollisionObject other) { 
     if (other is Box) { 
      return Collision.BetweenBoxSphere((Box)other, this); 
     } 
    } 
} 

class Collision { 
    public static Collision BetweenBoxSphere(Box b, Sphere s) { ... } 
} 

Ecco in Common Lisp:

(defmethod check-for-collision-with ((x box) (y sphere)) 
    (box-sphere-collision x y)) 

(defmethod check-for-collision-with ((x sphere) (y box)) 
    (box-sphere-collision y x)) 

(defun box-sphere-collision (box sphere) 
    ...) 
+0

Questo sembra essere molto difficile da mantenere, per ogni classe oggetto derivante da Collision faresti aggiungere un nuovo 'checkForCollisionWithX' ​​in tutte le altre classi derivate. – chelmertz

+0

È assolutamente difficile da mantenere. Non ho mai detto che questa fosse la tecnica giusta da utilizzare per il rilevamento delle collisioni. In realtà, il codice dovrebbe ** automaticamente ** costruire una matrice di invio collisioni. Le operazioni di collisione dovrebbero quindi essere gestite attraverso quella matrice. Si codifica il generatore di matrice una volta, si utilizza il riflesso, tutto procede in modo piacevole e veloce dopo. –

0

Si dovrebbe usare checkForCollisionWith: aCollisionObject e dal momento che tutti gli oggetti stanno estendendo CollisionObject si può mettere tutta la logica comune lì.

In alternativa è possibile utilizzare delegation design pattern per condividere la logica comune tra classi diverse.

1

Non si dice quale lingua si sta utilizzando, quindi presumo che sia qualcosa come Java o C#.

Questa è una situazione in cui i multimetodi sarebbero una soluzione ideale, ma la maggior parte delle lingue non li supporta. Il solito modo di emularli è con qualche variazione del pattern del visitatore - vedi qualsiasi buon libro sui modelli di design.

In alternativa, disporre di una classe CollisionDetection separata che controlla le collisioni tra coppie di oggetti e, se due oggetti si scontrano, chiama i metodi appropriati sugli oggetti, ad es. bomb.explode() e player.die(). La classe può avere una grande tabella di ricerca con ogni tipo di oggetto lungo le righe e le colonne e le voci che forniscono i metodi per chiamare su entrambi gli oggetti.

+0

Obiettivo-C. Suppongo che il problema sia lo stesso con Java o C#, comunque. – Rudi

3

Questo è un tipico errore nello sviluppo OO. Una volta ho anche provato a risolvere le collisioni in questo modo - solo per fallire miseramente.

Questa è una questione di proprietà. La classe Box possiede davvero la logica di collisione con il cerchio? Perché non il contrario? Il risultato è duplicazione del codice o delega del codice di collisione da un cerchio all'altro. Entrambi non sono puliti. La doppia spedizione non risolve questo problema - lo stesso problema con la proprietà ...

Quindi hai ragione - hai bisogno di funzioni/metodi di terze parti che risolvono particolari collisioni e un meccanismo che seleziona la funzione corretta per due oggetti in collisione (qui può essere usato il doppio dispatch, ma se il numero di primitive di collisione è limitato allora probabilmente una matrice 2D di funtori è una soluzione più veloce con meno codice).

0

Prima opzione: Effettuare la collisione direzionale. Ad esempio, se la casella è fissa, non controlla le proprie collisioni con qualcos'altro; ma il cerchio in movimento controlla la collisione con la scatola (e altri oggetti stazionari). Questo non è intuitivo perché a tutte le nostre vite ci viene insegnato "reazioni uguali e contrarie". Trappola: gli oggetti in movimento duplicano le collisioni con altri oggetti in movimento.


Seconda opzione: dare ad ogni oggetto un numero ID univoco. Nel metodo di controllo delle collisioni, controllare la collisione solo se il primo parametro/oggetto ha un ID inferiore al secondo parametro.

Dire che la casella ha id = 2 e circle ha id = 5. Quindi, "box collides with circle" verrebbe eseguito, dal box.id < circle.id; ma poi, quando il cerchio sta controllando le collisioni, "il cerchio collide con la scatola" tornerà immediatamente senza controllare la collisione, perché la collisione sarebbe già stata controllata.

+0

Oh, hmm. Forse ho risolto un problema diverso da quello di cui stai parlando. Per risolvere il tuo problema, penso che utilizzerei Generics (java) o un metodo template (C++). – Ricket

Problemi correlati