Question

Je suis en train de mettre en œuvre le modèle de conception commande , mais je suis trébuchant accross un problème conceptuel. Disons que vous avez une classe de base et quelques sous-classes comme dans l'exemple ci-dessous:

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 chose est, à chaque fois () opérateur est appelé une instance de unecommandequelconque, je voudrais ajouter * à un pile (la plupart du temps à des fins d'annulation) en appelant la méthode de registre du Commandement. Je voudrais éviter d'appeler "registre" de unecommandequelconque :: operator () (), mais de l'avoir appelé automaticaly (someway ;-))

Je sais que lorsque vous construisez une sous-classe tels que unecommandequelconque, le constructeur de la classe de base est appelée automatiquement, pour que je puisse ajouter un appel à y « registre ». La chose que je ne veux pas appeler registre jusqu'à ce que l'opérateur () () est appelée.

Comment puis-je faire cela? Je suppose que ma conception est quelque peu erronée, mais je ne sais pas vraiment comment faire ce travail.

Était-ce utile?

La solution

Il semble que si vous pouvez bénéficier de l'idiome NVI (Interface non virtuel). Il l'interface de l'objet command aurait aucune méthode virtuelle, mais cela remettrait en points d'extension privé:

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

Il y a différents avantages à cette approche, première est que vous pouvez ajouter des fonctionnalités communes dans la classe de base. D'autres avantages sont que l'interface de votre classe et l'interface des points d'extension ne sont pas liés les uns aux autres, pour que vous puissiez offrir des signatures différentes dans votre interface publique et l'interface d'extension virtuelle. Recherche pour NVI et vous obtiendrez beaucoup plus et de meilleures explications.

Addendum: L'original article par Herb Sutter où il introduit le concept (encore sans nom )

Autres conseils

Séparer l'opérateur dans deux méthodes différentes, par exemple exécuter et executeImpl (pour être honnête, je ne aime pas vraiment l'opérateur ()). Commande :: Faire exécuter non-virtuelle, et Command :: executeImpl pur virtuel, puis laissez-commande :: exécuter effectuer l'enregistrement, puis l'appeler executeImpl, comme ceci:

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

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

En supposant qu'il est une application « normale » avec défont et refont, je ne voudrais pas essayer et mélanger la gestion de la pile avec les actions effectuées par les éléments de la pile. Il devient très compliqué si vous avez soit plusieurs chaînes undo (par exemple, plus d'un onglet d'ouverture), ou lorsque vous faites-Redo, où la commande doit savoir si s'ajouter à annuler ou se déplacer de redo undo, ou se déplacer d'undo à refaire. Cela signifie aussi que vous avez besoin de se moquer de l'undo / redo pile pour tester les commandes.

Si vous voulez les mélanger, alors vous aurez trois méthodes de modèle, chacun prenant les deux piles (ou les besoins d'objets de commande pour avoir des références aux piles, il fonctionne sur lors de sa création), et chacun effectuant le déplacement ou ajouter , puis en appelant la fonction. Mais si vous n'avez ces trois méthodes, vous verrez qu'ils ne le font pas en réalité rien d'autre que les fonctions publiques d'appel sur la commande et ne sont pas utilisés par une autre partie de la commande, afin de devenir candidats la prochaine fois que vous Refactor votre code pour la cohésion.

Au lieu de cela, je crée une classe UndoRedoStack qui a une fonction execute_command (Commande * de commande), et laisse la commande aussi simple que possible.

En fait la suggestion de Patrick est le même que David qui est aussi le même que le mien. Utilisez NVI (langage d'interface de non-virtuelle) à cet effet. interfaces virtuelles pures manquent tout type de contrôle centralisé. Vous pourriez aussi créer une classe de base abstraite séparée que toutes les commandes héritent, mais pourquoi la peine?

Pour une discussion détaillée sur les raisons NVI sont souhaitables, voir C ++ normes de codage par Herb Sutter. Là, il va jusqu'à suggérer d'apporter toutes les fonctions publiques non virtuelle pour obtenir une séparation stricte du code de Overridable à partir du code d'interface publique (qui ne devrait pas être Overridable de sorte que vous pouvez toujours avoir un contrôle centralisé et ajouter l'instrumentation, pré / post vérification de l'état, et tout ce dont vous avez besoin).

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

Si nous prenons un pas en arrière et de regarder le problème général au lieu de l'être question immédiatement demandé, je pense que Pete offre de très bons conseils. Faire un commandement responsable de lui-même ajouter à la pile undo est pas particulièrement flexible. Il peut être indépendant du récipient dans lequel il réside. Ces responsabilités de niveau supérieur devraient probablement être une partie du conteneur réel que vous pouvez également responsable de l'exécution et à l'annulation de la commande.

Néanmoins, il devrait être très utile pour étudier NVI. Je l'ai vu trop de développeurs à écrire des interfaces virtuelles pures comme celui-ci sur les avantages historiques qu'ils avaient seulement ajouter le même code à chaque sous-classe qui définit quand il n'a besoin d'être mis en œuvre dans un seul endroit central. Il est un outil très pratique pour ajouter à votre boîte à outils de programmation.

J'ai eu un projet pour créer une application de modélisation 3D et que je l'habitude d'avoir la même exigence. Pour autant que je compris lorsque l'on travaille à ce sujet était que peu importe quoi et le fonctionnement doit toujours savoir ce qu'il a fait et devrait donc savoir comment le défaire. Donc, j'avais une classe de base créée pour chaque opération et son état de fonctionnement, comme indiqué ci-dessous.

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

Création d'une fonction et de l'état de ce serait:

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

Il y avait autrefois une classe appelée OperationManager. Ce différentes fonctions enregistrées et créé des instances de leur sein comme:

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

La fonction de registre était comme:

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

Chaque fois qu'une fonction devait être exécuté, il serait basé sur les objets actuellement sélectionnés ou tout ce qu'il a besoin de travailler. NOTE: Dans mon cas, je ne l'ai pas besoin d'envoyer les détails de la quantité de chaque objet doit se déplacer parce qui a été calculé par MoveOperation du dispositif d'entrée une fois qu'il a été défini comme la fonction active
. Dans le OperationManager, l'exécution d'une fonction serait comme:

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

Quand il y a une nécessité d'annuler, vous le faites à partir du OperationManager comme:
OperationManager::GetInstance().undo();
Et la fonction d'annulation des regards OperationManager comme ceci:

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

fait la OperationManager pas être au courant de ce que les arguments des besoins de chaque fonction et qu'il était donc facile à gérer différentes fonctions.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top