Pregunta

Estoy tratando de poner en práctica el comando patrón de diseño , pero estoy al otro lado de tropiezo un problema conceptual. Digamos que usted tiene una clase base y algunas subclases, como en el siguiente ejemplo:

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 es, cada vez () operador se llama en una instancia algúncomando, me gustaría añadir * esto a una pila (sobre todo con fines de deshacer) llamando al método de registro del sistema. Me gustaría evitar llamar "registro" de algúncomando :: operador () (), pero para que se llama de forma automática (de alguna manera ;-))

Sé que cuando se construye una subclase como algúncomando, el constructor de la clase base se denomina de forma automática, por lo que podría añadir una llamada a "registrar" allí. Lo que no quiero llamar a inscribirse hasta el operador () () se llama.

¿Cómo puedo hacer esto? Creo que mi diseño es defectuoso algo, pero no se sabe muy bien cómo hacer este trabajo.

¿Fue útil?

Solución

Parece como si usted puede beneficiarse de la expresión idiomática NVI (Interfaz no virtual). Existe la interfaz del objeto command no tendrían métodos virtuales, pero pondría en puntos de extensión privadas:

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

Hay diferentes ventajas de este enfoque, primero de los cuales es que se puede añadir funcionalidad común en la clase base. Otras ventajas son que la interfaz de su clase y la interfaz de los puntos de extensión no están unidos entre sí, por lo que podría ofrecer diferentes firmas en su interfaz pública y la interfaz de extensión virtual. Buscar NVI y obtendrá mucho más y mejores explicaciones.

Adición: El original por Herb Sutter donde se introduce el concepto (aún sin nombre )

Otros consejos

Split el operador en dos métodos diferentes, por ejemplo ejecutar y executeImpl (para ser honesto, no me gusta mucho el operador ()). Hacer Comando :: ejecutar no virtual, y el Comando :: executeImpl virtual pura, y luego dejar Comando :: execute realizar el registro, entonces lo llaman executeImpl, como esto:

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

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

Si se asume que es una aplicación de 'normal' con deshacer y rehacer, no intentaría y mezclar la gestión de la pila con las acciones realizadas por los elementos de la pila. Se pondrá muy complicado si bien tiene varias cadenas de anulación (por ejemplo, más de una pestaña abierta), o cuando lo hace-deshacer-rehacer, donde el comando tiene que saber si se debe añadir en sí para deshacer o moverse a sí mismo a partir de rehacer Para deshacer, o moverse a sí mismo a partir de deshacer para rehacer. También significa que usted necesita para burlar la acción de deshacer / rehacer pila para probar los comandos.

Si usted quiere mezclar ellos, entonces usted tendrá tres métodos plantilla, cada uno tomando las dos pilas (o las necesidades objeto de comando para tener referencias a las pilas que opera en el momento de su creación), y cada uno realizando el movimiento o complemento , a continuación, llamar a la función. Pero si usted tiene estos tres métodos, se verá que en realidad no hacen distinta de la función pública de compra sobre el comando nada y no son utilizados por cualquier otra parte del comando, por lo que los candidatos convertirse en la próxima vez que refactorizar su código para la cohesión.

En su lugar, me gustaría crear una clase que tiene un UndoRedoStack execute_command (* Comando de comandos) la función, y dejo el comando tan simple como sea posible.

Básicamente sugerencia de Patrick es el mismo que el de David, que también es la misma que la mía. Uso NVI (lenguaje de interfaz no virtual) para este fin. interfaces virtuales puras carecen de cualquier tipo de control centralizado. Se puede crear alternativamente, una clase base abstracta separada que heredan todos los comandos, pero ¿por qué molestarse?

Para una discusión detallada de por qué NVIS son deseables, ver C ++ Estándares de Codificación por Herb Sutter. Allí se va tan lejos como para sugerir hacer todas las funciones públicas no virtual para lograr una separación estricta del código reemplazable del código de la interfaz pública (que no debe ser reemplazable por lo que siempre se puede tener cierto control centralizado y añadir instrumentación, pre / post la comprobación de la condición, y todo lo que necesita).

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 tomamos un paso atrás y mirar el problema general en lugar de la cuestión inmediata se les pide, creo que Pete ofrece muy buenos consejos. Haciendo Comando responsable de sumarse a la pila de deshacer no es especialmente flexible. Puede ser independiente del contenedor en el que reside. Esas responsabilidades de nivel más alto, probablemente, debe ser una parte del propio recipiente, que también se puede hacer responsable de ejecutar y deshacer el comando.

Sin embargo, debe ser muy útiles para estudiar NVI. He visto a muchos desarrolladores a escribir interfaces virtuales puras como éste fuera de los beneficios históricos sólo tenían que añadir el mismo código para cada subclase que lo define cuando sólo necesita ser implementado en un lugar central. Es una herramienta muy útil para añadir a su caja de herramientas de programación.

Una vez tuve un proyecto para crear una aplicación de modelado 3D y por eso solía tener el mismo requisito. Por lo que he entendido cuando se trabaja en él fue que no importa qué y operación siempre debe saber lo que se hizo y por lo tanto, debe saber cómo deshacerlo. Así que tuve una clase base creada para cada operación y del estado de operación, como se muestra a continuación.

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

Creación de una función y su estado sería como:

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

No solía ser una clase llamada OperationManager. Esta registrado diferentes funciones y creó instancias de ellos dentro de ella como:

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

La función de registro fue como:

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

Siempre que una función iba a ser ejecutado, que se basaría en los objetos seleccionados en ese momento o lo que el necesita para trabajar. NOTA: En mi caso, no necesita enviar los detalles de la cantidad de cada objeto debe moverse debido a que estaba siendo calcula MoveOperation desde el dispositivo de entrada una vez que se estableció como la función activa
. En el OperationManager, la ejecución de una función sería:

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

Cuando hay una necesidad de deshacer, que hace que desde el OperationManager como:
OperationManager::GetInstance().undo();
Y la función de deshacer de las miradas OperationManager como esto:

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

Esto hizo que el OperationManager no ser consciente de qué argumentos las necesidades de cada función y así era fácil de manejar diferentes funciones.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top