C++ QT 设计模式之命令模式实现undo,redo

设计模式之命令模式

什么是命令模式?

引用《游戏设计模式》的一段话

命令模式是我最喜欢的模式之一。 大多数我写的游戏或者别的什么之类的大型程序,都会在某处用到它。
当在正确的地方使用时,它可以将复杂的代码清理干净。 对于这样一个了不起的模式,不出所料地,GoF有个深奥的定义:

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的操作。
我想你也会觉得这个句子晦涩难懂。 第一,它的比喻难以理解。
在词语可以指代任何事物的狂野软件世界之外,“客户”是一个人——那些和你做生意的人。 据我查证,人类不能被“参数化”。

然后,句子余下的部分介绍了可能会使用这个模式的场景。 如果你的场景不在这个列表中,那么这对你就没什么用处。 我的命令模式精简定义为:

命令是具现化的方法调用。

我理解为把行为封装成类调用,这样保存到了行为对象的状态,方便了实现undo,redo。


借助QT我们实现一个简单的命令模式的undo和redo的方法。


实现undo、redo

C++ QT 设计模式之命令模式实现undo,redo_第1张图片
C++ QT 设计模式之命令模式实现undo,redo_第2张图片

本程序通过QT自带的二维坐标类生成随机坐标,模拟游戏编辑器的放置物品的操作,并记录下来他们位置。
通过实现command类,使操作具象化。如果不懂QT的小伙伴,完全也可以仿照着写一下。不同的是,我使用了QT的信号和槽函数绑定了do,undo,redo的操作。不使用QT的朋友可以在invoker中进行操作判断,这点在文章开头的书籍中有详细的伪代码展示。

widget.h

#ifndef WIDGET_H
#define WIDGET_H
#include 
#include 
#include "invoker.h"
namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();
    void Init();
signals:
private slots://QT槽函数
    void on_undo_clicked();

    void on_doCommand_clicked();

    void on_redo_clicked();

private:
    Ui::Widget *ui;
    Invoker* m_invoker;
};

#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget),
    m_invoker(new Invoker)
{
    ui->setupUi(this);
    Init();
}
void Widget::Init()//qt绑定信号和槽函数,SIGNAL可以更改为clicked,我的电脑clicked总是执行两次,原因未知。
{
    connect(ui->doCommand,SIGNAL(doubleClicked()),this,SLOT(on_doCommand_clicked()));
    connect(ui->undo,SIGNAL(doubleClicked()),this,SLOT(on_undo_clicked()));
    connect(ui->redo,SIGNAL(doublelicked()),this,SLOT(on_redo_clicked()));
}
Widget::~Widget()
{
    delete ui;
}

void Widget::on_undo_clicked()
{
    m_invoker->undoCommand();//执行undocommand
}

//我采用了随机数来生成随机point坐标
void Widget::on_doCommand_clicked()
{
    qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
    int x =qrand()%10,y=qrand()%10;
    m_invoker->doCommand(new MoveUnitCommand(QPoint(x,y)));
}

void Widget::on_redo_clicked()
{
    m_invoker->redoCommand();//执行redocommand
}

command.h
作为命令类的父类,抽象了excute,undo,redo函数。以便各种不同的命令进行扩展。

#ifndef COMMAND_H
#define COMMAND_H

#include 
#include 
#include 
#include 
#include 

class Command
{
public:
    explicit Command();
    virtual ~Command() {} ;
    virtual void excute() = 0;
    virtual void undo() = 0;
    virtual void redo() = 0;
    virtual QPoint getpos() = 0;
private:
    QPoint cur_pos;
};
#endif // COMMAND_H

command.h并未有函数实现

moveUnitCommand.h作为移动unit的命令子类。我们在此实现了移动unit的抽象方法。

#ifndef MOVEUNITCOMMAND_H
#define MOVEUNITCOMMAND_H
#include "command.h"

class MoveUnitCommand:public Command
{
public:
    MoveUnitCommand(QPoint pos)
    : pos_(pos){}
    virtual void excute();
    virtual void undo();
    virtual void redo();
    virtual QPoint getpos(){ return pos_;}
private:
    QPoint pos_;

};

#endif // MOVEUNITCOMMAND_H
#include "moveunitcommand.h"

void MoveUnitCommand::excute()
{
    qDebug()<<"Move uint to"<<pos_;
}

void MoveUnitCommand::undo()
{
    qDebug()<<"undo MoveUnitCommand"<<pos_;
}

void MoveUnitCommand::redo()
{
    qDebug()<<"redo MoveUnitCommand"<<pos_;
}

invoker.h
invoker在游戏程序中也可以是handler,是一个承上启下的中控器
想要实现undo,redo,他是必不可少的一环。
在该类中我们使用了双栈的方法来实现undo、redo的操作。
逻辑如下:
每当执行了doCommand函数。就将新生成的command子类,入undo栈,如果此时redo栈非空则清空。
每当进行undo操作,弹出undo栈顶,入redo栈。

#ifndef INVOKER_H
#define INVOKER_H

#include "command.h"
#include "moveunitcommand.h"
class Invoker
{
public:
    Invoker();
    void doCommand(Command * curCommand);
    void undoCommand();
    void redoCommand();
public:
    Command* p_curcommand;
    QVector<Command*> undoStack;
    QVector<Command*> redostack;
    int pos;
};

#endif // INVOKER_H

invoker.cpp

#include "invoker.h"

Invoker::Invoker()
{
    p_curcommand = nullptr;
    pos = 0;
}


void Invoker::doCommand(Command *curCommand)
{
    curCommand->excute();
    undoStack.push_front(curCommand);
    if(!redostack.isEmpty())
    {
        redostack.clear();
    }
}

void Invoker::undoCommand()
{
    if(!undoStack.isEmpty())
    {
       Command* pop = undoStack.first();
       undoStack.pop_front();
       pop->undo();
       redostack.push_front(pop);
    }
}


void Invoker::redoCommand()
{
    if (!redostack.isEmpty()) {
        Command * pop = redostack.first();
        redostack.pop_front();
        pop->redo();
    }
}

显示结果:
Move uint to QPoint(3,5)
Move uint to QPoint(6,5)
Move uint to QPoint(6,5)
Move uint to QPoint(6,2)
undo MoveUnitCommand QPoint(6,2)
redo MoveUnitCommand QPoint(6,2)
undo MoveUnitCommand QPoint(6,5)
undo MoveUnitCommand QPoint(6,5)
redo MoveUnitCommand QPoint(6,5)

代码十分简单,但是命令模式高效强大,在编辑类大型程序,如记事本,游戏编辑器中无往不利。
希望本文能带给大家帮助。

你可能感兴趣的:(QT,设计模式,设计模式,c++,游戏开发)