Domanda

Sto cercando di implementare la comando design pattern , ma sto inciampo accross un problema concettuale. Diciamo che si dispone di una classe di base e alcune sottoclassi, come nel seguente esempio:

class Command : public boost::noncopyable {
    virtual ResultType operator()()=0;

    //Restores the model state as it was before command's execution.
    virtual void undo()=0;

    //Registers this command on the command stack.
    void register();
};


class SomeCommand : public Command {
    virtual ResultType operator()(); // Implementation doesn't really matter here
    virtual void undo(); // Same
};

La cosa è, ogni volta () operatore è chiamato su un'istanza SomeCommand, vorrei aggiungere * questo per uno stack (per lo più per scopi di undo) chiamando il metodo registro del Comando. Vorrei evitare di chiamare "registrare" da SomeCommand :: operator () (), ma di averlo chiamato automaticamente (in qualche modo ;-))

So che quando si costruisce una classe secondaria, come SomeCommand, il costruttore della classe base è chiamato automaticamente, in modo da poter aggiungere una chiamata a "registrare" lì. La cosa che non voglio chiamare registro fino a quando viene chiamato operatore () ().

Come posso fare questo? Credo che il mio design è un po 'viziata, ma io non so davvero come fare questo lavoro.

È stato utile?

Soluzione

Sembra come se si può beneficiare l'idioma NVI (non virtuale). C'è l'interfaccia dell'oggetto command non avrebbe alcun metodi virtuali, ma sarebbe mettere in punti di estensione privati:

class command {
public:
   void operator()() {
      do_command();
      add_to_undo_stack(this);
   }
   void undo();
private:
   virtual void do_command();
   virtual void do_undo();
};

Ci sono diversi vantaggi di questo approccio, prima delle quali è che è possibile aggiungere funzionalità comuni nella classe base. Altri vantaggi sono che l'interfaccia della classe e l'interfaccia dei punti di estensione, non è destinata a vicenda, in modo si potrebbe offrire diverse firme nell'interfaccia pubblica e l'interfaccia estensione virtuale. Cercare NVI e si ottengono spiegazioni molto di più e meglio.

Addendum: L'originale da Herb Sutter, dove egli introduce il concetto (ancora senza nome )

Altri suggerimenti

Split l'operatore in due metodi diversi, ad esempio esecuzione e executeImpl (ad essere onesti, non mi piace molto l'operatore ()). Fai Comando :: eseguire non virtuale, e Command :: executeImpl pura virtuale, poi lasciare Command :: execute eseguire la registrazione, poi lo chiamano executeImpl, in questo modo:

class Command
   {
   public:
      ResultType execute()
         {
         ... // do registration
         return executeImpl();
         }
   protected:
      virtual ResultType executeImpl() = 0;
   };

class SomeCommand
   {
   protected:
      virtual ResultType executeImpl();
   };

Supponendo che sia una domanda di 'normale' con Annulla e Ripristina, non vorrei provare a mescolare la gestione dello stack con le azioni eseguite dagli elementi nello stack. Si otterrà molto complicato se si hanno o più catene di annullamento (per esempio più di una scheda aperta), o quando si fa-scuci-cuci, in cui il comando deve sapere se aggiungere stesso per annullare o si passare da rifare a undo, o si sposta da annullare rifare. Significa anche che devi prendere in giro il undo / redo pila per testare i comandi.

Se volete mescolare loro, allora si avrà tre metodi di modello, ognuno prendendo le due pile (o le esigenze di comando degli oggetti da avere i riferimenti alle pile opera sul momento della creazione), e ciascuno di eseguire lo spostare o aggiungere , quindi chiamando la funzione. Ma se si dispone di questi tre metodi, vedrete che in realtà non fanno altro che funzioni pubbliche di chiamata sul comando e non sono utilizzati da qualsiasi altra parte del comando, i candidati in modo da diventare la prossima volta che refactoring del codice per la coesione.

Invece, mi piacerebbe creare una classe UndoRedoStack che ha una funzione di execute_command (Comando * di comando), e lasciare il comando più semplice possibile.

In pratica il suggerimento di Patrick è la stessa di David di che è anche uguale al mio. Usa NVI (interfaccia linguaggio non virtuale) per questo scopo. interfacce virtuali puri non hanno alcun tipo di controllo centralizzato. Si potrebbe in alternativa creare una classe base astratta separata che tutti i comandi ereditano, ma perché preoccuparsi?

Per una discussione dettagliata sul perché NViS sono desiderabili, vedi C ++ Coding Standards da Herb Sutter. Ci si va al punto di suggerire fare tutte le funzioni pubbliche non virtuale per ottenere una netta separazione tra il codice override dal codice interfaccia pubblica (che non dovrebbe essere overridable modo che si può sempre avere un certo controllo centralizzato e aggiungere strumentazione, pre / post condizione di controllo, e quant'altro è necessario).

class Command 
{
public:
   void operator()() 
   {
      do_command();
      add_to_undo_stack(this);
   }

   void undo()
   {
      // This might seem pointless now to just call do_undo but 
      // it could become beneficial later if you want to do some
      // error-checking, for instance, without having to do it
      // in every single command subclass's undo implementation.
      do_undo();
   }

private:
   virtual void do_command() = 0;
   virtual void do_undo() = 0;
};

Se prendiamo un passo indietro e guardare il problema generale invece della questione immediata stato chiesto, credo che Pete offre alcuni molto buoni consigli. Rendendo Comando responsabile aggiungendosi alla stack di annullamento non è particolarmente flessibile. Può essere indipendente dal contenitore in cui si trova. Le responsabilità di livello superiore dovrebbero probabilmente essere una parte del contenitore reale che si può anche fare responsabile dell'esecuzione e disfare il comando.

Tuttavia, dovrebbe essere molto utile per studiare NVI. Ho visto troppi sviluppatori di scrivere le interfacce virtuali puri come questo fuori dei benefici storici avevano solo per aggiungere lo stesso codice per ogni sottoclasse che lo definisce quando deve essere attuata solo in un posto centrale. Si tratta di uno strumento molto utile per aggiungere alla vostra cassetta degli attrezzi di programmazione.

Una volta ho avuto un progetto per creare un'applicazione di modellazione 3D e per questo ho usato per avere la stessa esigenza. Per quanto ho capito quando si lavora su di esso era che non importa cosa e il funzionamento dovrebbe sempre sapere quello che ha fatto e, pertanto, dovrebbe sapere come annullarla. Così ho avuto una classe di base creata per ogni operazione ed è stato di funzionamento come illustrato di seguito.

class OperationState
{
protected:
    Operation& mParent;
    OperationState(Operation& parent);
public:
    virtual ~OperationState();
    Operation& getParent();
};

class Operation
{
private:
    const std::string mName;
public:
    Operation(const std::string& name);
    virtual ~Operation();

    const std::string& getName() const{return mName;}

    virtual OperationState* operator ()() = 0;

    virtual bool undo(OperationState* state) = 0;
    virtual bool redo(OperationState* state) = 0;
};

Creazione di una funzione e di Stato sarebbe come:

class MoveState : public OperationState
{
public:
    struct ObjectPos
    {
        Object* object;
        Vector3 prevPosition;
    };
    MoveState(MoveOperation& parent):OperationState(parent){}
    typedef std::list<ObjectPos> PrevPositions;
    PrevPositions prevPositions;
};

class MoveOperation : public Operation
{
public:
    MoveOperation():Operation("Move"){}
    ~MoveOperation();

    // Implement the function and return the previous
    // previous states of the objects this function
    // changed.
    virtual OperationState* operator ()();

    // Implement the undo function
    virtual bool undo(OperationState* state);
    // Implement the redo function
    virtual bool redo(OperationState* state);
};

Ci deve essere utilizzato una classe chiamata OperationManager. Questo registrata funzioni diverse e ha creato le istanze di loro all'interno di esso come:

OperationManager& opMgr = OperationManager::GetInstance();
opMgr.register<MoveOperation>();

La funzione di registro è stato come:

template <typename T>
void OperationManager::register()
{
    T* op = new T();
    const std::string& op_name = op->getName();
    if(mOperations.count(op_name))
    {
        delete op;
    }else{
        mOperations[op_name] = op;
    }
}

Ogni volta che una funzione doveva essere eseguito, si sarebbe basato sugli oggetti attualmente selezionati o qualunque cosa di cui ha bisogno per lavorare. NOTA: Nel mio caso, non ho bisogno di inviare i dettagli di quanto ogni oggetto deve muoversi perché che veniva calcolato MoveOperation dal dispositivo di input, una volta che è stato impostato come la funzione attiva
. Nel OperationManager, l'esecuzione di una funzione sarebbe come:

void OperationManager::execute(const std::string& operation_name)
{
    if(mOperations.count(operation_name))
    {
        Operation& op = *mOperations[operation_name];
        OperationState* opState = op();
        if(opState)
        {
            mUndoStack.push(opState);
        }
    }
}

Quando c'è una necessità di undo, fate che dalla OperationManager come:
OperationManager::GetInstance().undo();
E la funzione di undo dei look OperationManager in questo modo:

void OperationManager::undo()
{
    if(!mUndoStack.empty())
    {
        OperationState* state = mUndoStack.pop();
        if(state->getParent().undo(state))
        {
            mRedoStack.push(state);
        }else{
            // Throw an exception or warn the user.
        }
    }
}

Ciò ha reso l'OperationManager non essere a conoscenza di quali argomenti ogni esigenze di funzionalità e così è stato facile da gestire diverse funzioni.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top