2012-02-15 10 views
10

Recentemente ho visto una domanda di progettazione OO su alcuni forum e ho iniziato a pensare di usare RTTI. Tuttavia questo deve essere un cattivo design, ma non riesco a pensare a un'alternativa. Ecco la semplice domanda:evitare RTTI in OO design

creare un programma C++ per il seguente scenario utilizzando concetti OO -

Il mio cane, di nome Buddy vive nel cortile di casa. Abbaia di notte quando vede un gatto o uno scoiattolo che è venuto a visitare. Se vede una rana e ha fame, la mangia. Se vede una rana e non ha fame, gioca con lui. Se ha già mangiato 2 rane e ha ancora fame, lo lascerà andare. Se vede un coyote, cerca aiuto. A volte il suo amico Spot si ferma e si rincorrono. Se vede altri animali, lo guarda semplicemente. Mi aspetto che tu abbia una classe di animali e una classe di cani, cani, scoiattoli, coyote che erediti dalla classe degli animali.

ho iniziato a pensare di avere un metodo See() nella classe cane che prende un argomento di animali e poi controlla il tipo effettivo dell'oggetto (rana, gatto, ecc) e prende l'azione necessaria - play, caccia, ecc a seconda del tipo effettivo. Tuttavia ciò richiederebbe RTTI che deve essere una cattiva progettazione. Qualcuno può suggerire un design migliore che eviti RTTI e segnala anche l'errore nel mio modo di pensare?

+1

Penso che questa domanda appartiene a programmers.stackexchange.com. –

+1

Una nota ** molto ** importante è RTTI, in quanto gestita * dal compilatore *, non è l'unico modo per un oggetto di identificarsi. Potresti anche avere un metodo 'enum AnimalTypes' e' virtual Animal :: GetType() ', o un metodo' Uuid Unknown :: GetClass() '(un COM 'IUnknown :: QueryInterface') o anche un problema specifico 'Animale :: IsScarierThan (Animal * me)'. Esistono diversi modi per utilizzare la progettazione orientata agli oggetti per ottenere informazioni sul tipo di runtime, senza utilizzare RTTI come tale. – ssube

+3

* che è venuto a visitare * -> vuoi dire che il gatto e lo scoiattolo sono * visitatori *;)? –

risposta

13

Esistono un numero ridicolmente elevato di modi per soddisfare questo problema utilizzando "Concetti OO", a seconda di ciò che si desidera sottolineare.

Ecco la soluzione più semplice che posso venire con:

class Animal { 
public: 
    virtual void seenBy(Buddy&) = 0; 
}; 

class Buddy { 
public: 
    void see(Cat&)  { /* ... */ } 
    void see(Squirrel&) { /* ... */ } 
    // ... 
}; 

class Cat : public Animal { 
public: 
    virtual seenBy(Buddy& b) { b.see(*this); } 
}; 

class Squirrel : public Animal { 
public: 
    virtual seenBy(Buddy& b) { b.see(*this); } 
}; 

// classes for Frog, Coyote, Spot... 

Se avete bisogno di più tipi di "percepire" animali, è semplice fare un involucro virtuale per see (la produzione di una forma di double dispatch):

// On a parent class 
virtual void see(Animal&) = 0; 

// On Buddy 
virtual void see(Animal& a) { a.seenBy(*this); } 

È possibile che questo richiede che la classe Animal sapere qualcosa circa il Buddy classe. Se non vi piacciono i vostri metodi di essere verbi passivi e vogliono disaccoppiare Animal da Buddy, è possibile utilizzare il modello visitatore:

class Animal { 
public: 
    virtual void visit(Visitor&) = 0; 
}; 

class Cat : public Animal { 
public: 
    virtual void visit(Visitor& v) { v.visit(*this); } 
}; 

class Squirrel : public Animal { 
public: 
    virtual void visit(Visitor& v) { v.visit(*this); } 
}; 

// classes for Frog, Coyote, Spot... 

class Visitor { 
public: 
    virtual void visit(Cat&) = 0; 
    virtual void visit(Squirrel&) = 0; 
    // ... 
}; 

class BuddyVision : public Visitor { 
public: 
    virtual void visit(Cat&)  { /* ... */ } 
    virtual void visit(Squirrel&) { /* ... */ } 
    // ... 
}; 

class Buddy { 
public: 
    void see(Animal& a) { 
     BuddyVision visitor; 
     a.visit(visitor); 
    } 
}; 

Il secondo meccanismo potrebbe essere utilizzato per scopi diversi amici che vedono un animale (forse per quell'animale che vede Buddy). È, tuttavia, più complicato.


Nota che OO non è sicuramente l'unico modo per risolvere questo problema. Esistono altre soluzioni che possono essere più pratiche per questo problema, come la memorizzazione delle proprietà dei vari animali che causano ad Abbaiare, mangiare, giocare, ecc. Questo disaccoppia inoltre la classe Buddy dalla classe Animal (anche il modello di visitatore ha bisogno di un elenco completo di tutto ciò che Buddy può percepire).

+0

Suggerimento: 'BuddyVision()' non può essere associato a un riferimento non const. Dovresti fornire un sovraccarico di "visita" o istanziarlo in pila come argomento con nome (sì, questo è stupido ...). –

+0

@MatthieuM. Risolto, grazie. –

+0

@John Calsbeek: Grazie mille !! – vjain27

3

Suggerimento: utilizzare le funzioni virtuali (sugli animali di destinazione) anziché RTTI.

+0

E il meccanismo delle funzioni virtuali non utilizza RTTI? o.O – lapk

+1

In realtà come lo faresti? Gli animali non dovrebbero sapere cosa fare quando sono visti da un cane, dovrebbero? Penserei che il cane avrebbe bisogno di identificare l'animale e fare ciò che voleva in base a ciò che percepiva l'animale come (un 'interruttore' in termini di codice). In questo caso, in realtà andrei con RTTI. –

+2

@AzzA no, utilizza l'invio dinamico, non RTTI –

6

Il progetto richiede espressamente il riconoscimento di determinate entità al fine di eseguire determinate operazioni su di esse. Poiché non vi è alcuna filastrocca o ragione per spiegare perché determinate operazioni vadano con determinate entità (ad esempio: è tutto arbitrario), ciò che si sta guardando è l'invio basato su tipo o l'invio basato sulla proprietà. Vorrei andare con quest'ultimo.

Assegnare a ciascuna entità un gruppo di proprietà. Il cane reagirebbe quindi in base a tali proprietà. Gatto e Scoiattolo avrebbero la proprietà, "Cane dovrebbe abbaiare contro di me". Quando il Cane incontra un'entità con tale proprietà, eseguirà l'azione appropriata.

In questo caso, un'entità non è altro che la somma delle sue proprietà, nonché i comportamenti basati sull'incontro con altre entità con varie proprietà. L'entità può anche avere qualche stato associato ad esso. Non ci sarebbe un cane o gatto specifico classe. Ci sarebbe solo un'entità con proprietà e comportamenti Cat-like, e un'entità con proprietà e comportamenti Dog-like.

+0

Questa è sicuramente una buona idea. È certamente un esempio del perché OO non è il fine-tutti-tutti i paradigmi di programmazione. Tuttavia, probabilmente vale anche la pena di imparare come l'OO "vero" risolverebbe il problema, come punto di confronto. Inoltre, la domanda è esplicitamente un esercizio di progettazione OO. (Questo presuppone che tu definisca OO come un vantaggio del polimorfismo, non semplicemente come un'associazione di dati e funzioni che operano su quei dati.) –

+0

@JohnCalsbeek Non riesco a vedere come questo sia in qualche modo "non OO". Anche se dovessi implementare un sistema di definizione delle entità basato sulla proprietà, utilizzerei un design OO per realizzarlo. Cosa diavolo costituisce "vero OO" in questo o in qualsiasi altro caso ?? Ho persino finito la mia laurea, e qui non vedo nulla che non rientri in ciò che può essere realizzato usando OOP. – TheBuzzSaw

+0

@TheBuzzSaw: Non parlare per John, ma personalmente, non considero un progetto "orientato agli oggetti" a meno che non implichi il polimorfismo tramite l'ereditarietà. Senza quello, non è niente che non potresti fare facilmente in C. –

0

La maggior parte delle volte è possibile sostituire RTTI tramite messaggistica.

sorta di

Id id = object->send(WHO_ARE_YOU); 
switch(id) 
{ 
    case ID_FROG: ...; break; 
    case ID_CAT: ...; break; 
} 

Messaging è più flessibile di RTTI in linea di principio:

other_object->send(IS_SCARRY_OF, this); 

in quanto consente di progettare le relazioni che sono sconosciuti al momento. Dì domani il tuo cane vedrà racoon che è definito in qualche altra DLL e ancora in Pascal.

+1

Questo non è altro che RTTI non basato su compilatore. È lo stesso concetto: gli oggetti hanno un identificatore che determina il loro tipo in fase di esecuzione. –

+0

@Nicol Bolas - non esattamente, vedere il testo a partire da "La messaggistica è più flessibile ..." RTTI descrive solo un "messaggio" - WHO_ARE_YOU di per sé. –

+1

Oppure potresti semplicemente avere una funzione chiamata "is_scarry_of" che accetta un oggetto. Tutto quello che stai facendo è utilizzare il passaggio dei messaggi per prendere il posto della spedizione virtuale e di RTTI. E quindi, perdere le caratteristiche linguistiche e le garanzie di entrambi. –