Frage

Ich versuche, die Befehl Entwurfsmuster zu implementieren, aber ich bin accross Stolper ein konzeptionelles Problem. Angenommen, Sie haben eine Basisklasse und einige Unterklassen wie im folgenden Beispiel:

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
};

Die Sache ist, jedes Mal Operator () auf einem einKommando Instanz aufgerufen wird, würde ich dies hinzufügen, * zu einem Stapel (meist für Undo-Zwecke) durch den Befehl des Registers Methode aufrufen. Ich möchte "registrieren" von einKommando :: operator () () zu vermeiden, fordern, aber habe es automaticaly genannt (someway ;-))

Ich weiß, dass, wenn Sie eine Unterklasse konstruieren wie einKommando, der Basisklassenkonstruktor automaticaly genannt wird, so dass ich einen Anruf auf „registrieren“ dort hinzufügen könnte. Das, was ich tun will nicht registrieren rufen, bis operator () () aufgerufen wird.

Wie kann ich das tun? Ich denke, mein Design ist etwas fehlerhaft, aber ich weiß nicht wirklich, wie diese Arbeit zu machen.

War es hilfreich?

Lösung

Es sieht aus, als ob Sie aus dem NVI (Non-Virtual Interface) Idiom profitieren können. Dort wird die Schnittstelle des command Objekt würde keine virtuellen Methoden, sondern in private Erweiterungspunkte nennen würde:

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

Es gibt verschiedene Vorteile dieser Vorgehensweise erste davon ist, dass man gemeinsame Funktionalität in der Basisklasse hinzufügen können. Weitere Vorteile sind, dass die Schnittstelle der Klasse und die Schnittstelle der Erweiterungspunkte nicht miteinander verbunden, so dass Sie verschiedene Signaturen in Ihrer öffentlichen Schnittstelle und die virtuellen Erweiterung Schnittstelle bieten konnten. Suchen Sie nach NVI und Sie werden viel mehr und bessere Erklärungen.

Nachtrag: Der ursprüngliche Artikel von Herb Sutter, wo er führt das Konzept (noch unbenannte )

Andere Tipps

Split der Bediener in zwei verschiedenen Verfahren, zum Beispiel ausführen und executeImpl (um ehrlich zu sein, ich weiß nicht wirklich wie der Operator ()). Make-Befehl :: execute nicht-virtuellen und Befehl :: executeImpl rein virtuell, dann lassen Sie Befehl :: führen Sie die Registrierung durchführen, dann executeImpl nennen, wie folgt aus:

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

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

Unter der Annahme, es ist eine ‚normale‘ Anwendung mit Undo und Redo, würde ich nicht versuchen, und mischen Sie den Stapel mit den Aktionen, die von den Elementen auf dem Stapel ausgeführt zu verwalten. Es wird noch sehr kompliziert, wenn Sie entweder mehrere Undo-Ketten (zB mehr als eine Registerkarte geöffnet) haben, oder wenn Sie das tun-Undo-Redo, wo der Befehl, ob sich hinzuzufügen, zu wissen, hat rückgängig zu machen oder sich von Redo rückgängig zu machen bewegen, oder bewegen sich von Undo-Redo. Es bedeutet auch, müssen Sie verspotten die Undo / Redo-Stapel, die Befehle zu testen.

Wenn Sie sie mischen wollen, dann werden Sie drei Template-Methoden haben, die jeweils die zwei Stapel (oder ein die Befehlsobjekt Bedürfnisse Verweise auf die Stapel, um es auf der arbeitet, wenn erstellt) und jede Durchführung der Bewegung oder Add Aufruf, dann die Funktion. Aber wenn man diese drei Methoden haben, werden Sie sehen, dass sie eigentlich nichts tun außer Anruf öffentlichen Funktionen auf dem Befehl und werden von keinem anderen Teil des Befehls verwendet, so werden Kandidaten das nächste Mal Refactoring Sie Ihren Code für den Zusammenhalt.

Stattdessen würde ich eine UndoRedoStack Klasse erstellen, die eine execute_command (Befehl * Befehl) Funktion hat, und den Befehl so einfach wie möglich verlassen.

Im Grunde Patrick Vorschlag ist die gleiche wie Davids, die auch das gleiche wie ich. Verwenden NVI (nicht-virtuelle Schnittstelle Idiom) für diesen Zweck. Rein virtuelle Schnittstellen fehlt jede Art von zentralisierten Kontrolle. Sie könnten alternativ eine separate abstrakte Basisklasse erstellen, dass alle Befehle erben, aber warum die Mühe machen?

Für ausführliche Diskussion darüber, warum NVIs sind wünschenswert, siehe C ++ Coding Standards von Herb Sutter. Dort ist er geht so weit, vorzuschlagen, alle öffentlichen Funktionen nicht-virtuelle machen eine strikte Trennung von overridable Code aus dem öffentlichen Interface-Code zu erreichen (was nicht so sein overridable soll, dass Sie immer eine zentrale Steuerung und Instrumentierung hinzuzufügen haben, Pre / Post Zustand Prüfung und was Sie sonst noch brauchen).

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;
};

Wenn wir einen Schritt zurück und Blick auf das allgemeine Problem anstelle der unmittelbaren Frage gebeten nehmen, denke ich, Pete einige sehr gute Beratung bietet. Herstellung Befehl für sich den Rückgängig-Stapel Zugabe ist nicht besonders flexibel. Er kann aus dem Behälter unabhängig sein, in dem er sich befindet. Diese übergeordneten Aufgaben sollten wahrscheinlich ein Teil des eigentlichen Behälters sein, die Sie auch verantwortlich für die Ausführung und Lösen des Befehls machen.

Dennoch sollte es sehr hilfreich sein NVI zu studieren. Ich habe zu viele Entwickler wie diese aus den historischen Vorteilen rein virtuelle Schnittstellen sie nur den gleichen Code für jede Unterklasse hinzuzufügen hatten schreiben gesehen, dass es definiert, wenn es braucht nur an einer zentralen Stelle durchgeführt werden. Es ist ein sehr praktisches Tool, um Ihre Programmierung Toolbox hinzufügen.

Ich hatte einmal ein Projekt eine 3D-Modellierung Anwendung zu erstellen und zu, dass ich die gleiche Anforderung verwendet haben. Soweit ich verstanden, wenn sie daran zu arbeiten war, dass, egal was und Betrieb sollten immer wissen, was es tat, und deshalb sollten wissen, wie sie rückgängig zu machen. Also habe ich eine Basisklasse hatte für jede Operation erstellt und ist es Betriebszustand wie unten gezeigt.

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;
};

Erstellen einer Funktion und der Zustand wäre wie:

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);
};

Es verwendet eine Klasse namens OperationManager. Diese unterschiedlichen Funktionen registriert und Instanzen von ihnen erstellt in ihm wie:

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

Die Registerfunktion war wie:

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;
    }
}

Jedes Mal, wenn eine Funktion ausgeführt werden, wäre es auf den aktuell ausgewählten Objekte basieren oder das, was es braucht, zu arbeiten. HINWEIS: In meinem Fall habe ich nicht die Details, wie viel jedes Objekt senden müssen sollte, weil bewegen, dass durch MoveOperation von der Eingabeeinrichtung berechnet wurde, sobald es als aktive Funktion gesetzt wurde
. Im OperationManager wäre eine Funktion auszuführen, wie:

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);
        }
    }
}

Wenn es eine Notwendigkeit rückgängig zu machen, tun Sie, dass aus den OperationManager wie:
OperationManager::GetInstance().undo();
Und die Undo-Funktion der OperationManager sieht wie folgt aus:

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.
        }
    }
}

Das machte die OperationManager nicht darüber im Klaren sein, was Argumente jeder Funktion Bedürfnisse und so war leicht unterschiedliche Funktionen zu verwalten.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top