2010-08-02 12 views
6

Sto usando una fabbrica astratta per creare componenti dell'interfaccia utente come finestre di dialogo. Il factory astratto utilizzato viene restituito da un "INode" generico attualmente selezionato che è la classe base per diversi tipi di nodi. Così, per esempio, se voglio aggiungere un nuovo nodo dello stesso tipo come il nodo selezionato, lo scenario più o meno così:Problema utilizzando la fabbrica astratta

(si prega di notare che questo è il codice semi-pseudo) scatta

utente nodo e il nodo viene memorizzato per un uso successivo:

void onTreeNodeSelected(INode *node) 
{ 
    selectedNode = node; 
} 

utente fa clic su "add" sull'interfaccia utente:

void onAddClicked() 
{ 
    IFactory *factory = selectedNode->getFactory(); 
    Dialog *dialog = factory->createAddDialog(parentWidget); 
    dialog->show(); 
} 

che tutto sembra bene. Il problema arriva quando voglio modificare il nodo selezionato:

void onEditClicked() 
{ 
    IFactory *factory = selectedNode->getFactory(); 
    Dialog *dialog = factory->createEditDialog(selectedNode, parentWidget); 
    dialog->show(); 
} 

Oh caro .. Sto passando un oggetto INode. A un certo punto dovrò ridimensionarlo al tipo di nodo corretto in modo che la finestra di dialogo possa usarlo correttamente.

Ho studiato il codice sorgente "PostgreSQL Admin 3" e fanno qualcosa di simile a questo. Si aggirano facendo qualcosa del genere:

FooObjectFactoryClass::createDialog(IObject *object) 
{ 
    FooObjectDialog *dialog = new FooObjectDialog((FooObject*)object); 
} 

Yeck .. cast!

L'unico modo che posso pensare intorno ad esso e ancora in grado di utilizzare le mie fabbriche è quello di iniettare il nodo stesso nella fabbrica prima di essere restituito:

FooNode : INode 
{ 
    FooNodeFactory* FooNode::getFactory() 
    { 
     fooNodeFactory->setFooNode(this); 
     return fooNodeFactory; 
    } 
} 

Allora la mia modifica evento può fare questo:

void onEditClicked() 
{ 
    IFactory *factory = selectedNode->getFactory(); 
    Dialog *dialog = factory->createEditDialog(parentWidget); 
    dialog->show(); 
} 

E utilizzerà il nodo inserito per il contesto.

Suppongo che se non vi è alcun codice inserito, createEditDialog potrebbe affermare false o qualcosa del genere.

Qualche idea?

Grazie!

risposta

3

Una soluzione comune è "double-dispatch", in cui si chiama una funzione virtuale su un oggetto, che a sua volta chiama una funzione virtuale sull'altro, passando this, che ora ha il tipo statico corretto. Quindi, nel tuo caso, la fabbrica può contenere "creare" le funzioni per i vari tipi di dialoghi:

class IFactory 
{ 
public: 
    .... 
    virtual Dialog* createEditDialog(ThisNode*, IWidget*); 
    virtual Dialog* createEditDialog(ThatNode*, IWidget*); 
    virtual Dialog* createEditDialog(TheOtherNode*, IWidget*); 
    .... 
}; 

quindi ogni tipo di nodo ha un virtuale createEditDialog che invia alla funzione di fabbrica corretto:

class INode 
{ 
public: 
    .... 
    virtual Dialog* createEditDialog(IWidget* parent) = 0; 
    .... 
}; 

class ThisNode : public INode 
{ 
public: 
    .... 
    virtual Dialog* ThisNode::createEditDialog(IWidget* parent) 
    { 
     return getFactory()->createEditDialog(this, parent); 
    } 
    .... 
}; 

quindi è possibile creare il dialogo corretto come

void onEditClicked() 
{ 
    Dialog *dialog = selectedNode->createEditDialog(parentWidget); 
    dialog->show(); 
} 
1

A mio parere, l'utilizzo di cast in stile C (anche se lo stile C++ sarebbe preferibile) è perfettamente accettabile a condizione che il codice sia correttamente commentato.

Non sono un grande fan di DI (dependency injection) perché rende difficile la lettura di alcuni codici e, nel tuo caso, preferirei guardare uno dynamic_cast<>() o qualcosa di simile piuttosto che provare a seguire il codice iniettato su più file sorgente.

0

Vorrei aggiungere due cose.

Primo: non c'è niente di sbagliato nel casting. Se vuoi essere sicuro, puoi utilizzare RTTI (tipo_id stuff) o alcune funzioni virtuali nella classe INode che potrebbero restituire alcune informazioni che ti consentirebbero di sapere se è sicuro trasmettere.

Secondo: è possibile controllare per vedere a cosa serve la funzione createEditDialog e inserirli in funzioni virtuali in INode o in una classe ereditata che sarebbe il tipo che createDialog si aspetta.

In generale, non vedo nulla di veramente sbagliato nel problema che descrivi, ed è difficile dare più suggerimenti senza vedere l'intero codice, che presumo sia irrealizzabile.

0

il tuo approccio di iniettare il nodo nella fabbrica è in genere un modello che ho trovato utile, ma ci sono spesso quando non hai un riferimento all'oggetto target quando stai creando la fabbrica come fai qui. Quindi in questo caso potrebbe funzionare bene per te ed è più semplice che gestire questo tipo di problema nel caso generale.

Per il caso più generale, è necessario lavorare con la nozione di interfacce e stabilire un meccanismo mediante il quale l'oggetto INode può pubblicare le interfacce supportate e fornire l'accesso a tali interfacce per i client. Tutto ciò porta dinamicamente ad un approccio COM-like che richiede registrazione e casting dinamici. Ma puoi anche farlo in modo tipicamente statico se hai un set relativamente stabile di interfacce che vuoi esporre e puoi permetterti di modificare l'interfaccia INode quando devi aggiungere una nuova interfaccia componente.

Quindi questo sarebbe un esempio di come fare il semplice approccio a tipizzazione statica:

struct INode 
{ 
    virtual INodeSize* getNodeSizeInterface() = 0; 

    virtual INodeProperties* getNodePropertiesInterface() = 0; 

    virtual INodeColor* getNodeColorInterface() = 0; 

    ... // etc 
} 

Ora ogni INode implementazione può restituire alcune o tutte queste interfacce dei componenti (sarebbe solo restituire NULL se esso didn' li implementa). Quindi i dialoghi operano sulle interfacce dei componenti per fare il loro lavoro invece di cercare di capire quale implementazione effettiva di INode è stata inoltrata. Ciò renderà molto più flessibile la mappatura tra le finestre di dialogo e le implementazioni dei nodi. Una finestra di dialogo può determinare rapidamente se ha un oggetto "compatibile" INode verificando che restituisca un oggetto valido per ogni interfaccia a cui interessa la finestra di dialogo.

0

Penso che un cast all'interno di createEditDialog non sia una cosa negativa in questo caso , anche se si rinuncia ai controlli del tempo di compilazione. Se il tipo di nodo non cambia in fase di runtime, è possibile utilizzare i modelli anziché una classe astratta INode.

In caso contrario, la soluzione proposta è quella che vorrei anche pensare. Tuttavia, rinominerei il metodo in qualcosa come "getSelectedNodeDialogFactory" (lo so, nome lungo) in modo che sia chiaro che il factory restituito è specifico per quel nodo. Esistono altri dialoghi che devono conoscere il tipo concreto dell'oggetto INode? createAddDialog ha bisogno di un nodo genitore o predecessore, forse? Tutti possono andare nella classe di nodi con factory-selected-selected.

Problemi correlati