2010-09-10 7 views
5

Prima di dirmi che esiste già una domanda simile, sì, lo so, ho letto it. Ma la domanda ci si concentra su quando, sono interessato a perché.Un buon motivo per utilizzare il modello di progettazione Visitor?

Ho capito come funzionano le cose. Il classico esempio animale, cane, gatto funziona sempre come un incantesimo.

La cosa è questo codice

int main() 
{ 
    Cat c; 
    Sound theSound; 
    c.letsDo(&theSound); 
} 

sembra così innaturale per me. Perché?

Voglio dire, sì, in questo modo ho i miei modelli di cane e gatto indifferenziata (prima volta che uso questa parola in inglese btw) perché il vero implentation è nascosto sotto la classe Sound, ma non è che solo un modo per appesantire il tuo codice? Il polimorfismo non è abbastanza per fare qualcosa del genere?

Per me la differenza è che con il polimorfismo è necessario modificare ogni classe (ma il modello rimane lo stesso, giusto?) Mentre si deve solo modificare una classe con il modello di progettazione del visitatore.

risposta

8

Il modello di visitatore consente di fare qualcosa, che semplicemente non si basa sul polimorfismo: lavorare con casi d'uso inattesi. Se stai scrivendo una biblioteca, questo è un punto importante. Consentitemi di elaborare:

Considerate un esempio classico per l'utilizzo del pattern visitor, ovvero l'operazione sui nodi di qualche albero abstract syntax. Per aggiungere alcuni dettagli, ad esempio, hai appena scritto una libreria di parser per SQL, che prende le stringhe, le analizza e restituisce un AST per le cose trovate nell'input. A meno che tu non possa anticipare tutti i possibili casi d'uso che il tuo codice cliente potrebbe avere per un tale AST, devi fornire un modo "generico" per percorrere l'AST. Fornire funzioni di accesso come DOM (getNodeType, getParentNode, getPreviousNode) è unidirezionale. Il problema qui è che questo mette un pesante fardello sui client del tuo grimorio, perché hanno bisogno di fare lo stesso invio. Ancor più, hanno bisogno di sapere in modo molto dettagliato, che puntatori da seguire per ogni possibile tipo di nodo:

void 
walk_tree(AstNode* node) 
{ 
    switch(node->getNodeType()) { 
    case SELECT_NODE: 
     for(AstNode* child = node->getFirstChild(); child; child = child->getNextNode()) { 
      walk_tree(child); 
     } 
     break; 
    ... 
    } 
} 

Il modello visitatore si muove questo peso dal client nella libreria.

+0

Esattamente, il comportamento può essere implementato da una terza parte. –

+0

Ok, ma se si definisce parser per SQL non si ha già la definizione grammaticale? Dove sono i casi d'uso inattesi? In realtà dal momento che stiamo parlando di analisi, non dovrebbe essere un generatore di parser un esempio più appropriato? Lì hai una grammatica arbitraria, quindi devi definire una generica classe tree walker. – dierre

+0

Non si tratta della struttura di SQL. Riguarda il modo in cui il client può utilizzare il risultato/l'output della tua libreria. L'esempio del parser è semplicemente usato perché è un po '"classico". A proposito, se si dispone di un'API completamente generica (nodi "DOM" arbitrari) senza una struttura fissa, si potrebbe essere meglio con l'approccio dell'accessorio generico invece del modello di visitatore. – Dirk

3

Supponiamo che tu abbia alcune cose di base definite in una libreria che non possiedi e che devi estendere. Come:

// In base lib: 
interface ISomething { 
    void DoSomething(); 
} 

class Something1 : ISomething { 
    // ... 
} 

class Something2 : ISomething { 
    // ... 
} 

polimorfismo consente di definire nuove cose è possibile eseguire operazioni su:

// In your lib: 
class MySomething : ISomething { 
} 

E ora la lib di base può lavorare con il tuo MySomething come se fosse definito. Che cosa è non consente di aggiungere nuove operazioni . DoSomething è l'unica cosa che possiamo fare con un ISomething. Il pattern Visitor lo indirizza.

Il lato negativo è che utilizzando il modello di visitatore costa la possibilità di definire nuovi tipi come abbiamo appena mostrato.Il fatto che la maggior parte delle lingue consenta di aggiungere operazioni o tipi facilmente, ma non entrambi, è chiamato expression problem.

Il modello di visitatore è interessante, ma non ne ho mai trovato la necessità al di fuori dell'implementazione dei compilatori.

+0

Non è possibile aggiungere nuovi tipi perché il visitatore sarà ancora un componente della libreria, giusto? – dierre

+1

La classe visitatore verrà definita nella base lib e avrà un insieme fisso di metodi, uno per ogni tipo. Non è possibile aggiungere nuovi tipi nella libreria lib perché non è possibile modificare il visitatore e non è possibile spostare il visitatore nella libreria lib perché i tipi nella libreria di base devono fare riferimento ai metodi 'accept()'. – munificent

+0

capito. Grazie! – dierre

1

Ho usato il modello di visitatore quando avevo un albero di oggetti e avevo bisogno di stampare il contenuto in molti modi. Comma-sep, XML, qualunque cosa. Invece di aggiungere alla classe dell'albero un nuovo metodo di stampa per ogni formato di output, ho utilizzato il pattern visitor e creato le classi CommaSepVisitor, XMLVisitor e HTMLVisitor. Il codice dell'albero non è mai cambiato in quanto ho aggiunto più tipi di visitatori, quindi non ho mai introdotto bug. I visitatori stessi erano facili da scrivere.

2

Il pattern Visitor è molto utile.

Ci sono almeno tre grandi ragioni per utilizzarlo:

  1. ridurre la proliferazione di codice che è solo leggermente diversa quando strutture dati cambiano.

  2. Applicare lo stesso calcolo a diverse strutture di dati, senza modificare il codice che implementa il calcolo.

  3. Aggiungere informazioni alle librerie legacy senza modificare il codice legacy.

Si prega di dare un'occhiata a an article I've written about this.

Cheers

+0

tnx, lo leggerò. – dierre