篇一、
基本概念
命令模式(Command Pattern)是一种行为设计模式,它通过将请求封装为具有公共接口的对象,从而允许将请求参数化、队列化、保存和执行。
使用场景
当需要将请求发送者与请求接收者解耦的场景
当需要将操作进行排队的场景
当操作需要支持撤销的场景
当要对操作过程记录日志的场景
当操作需要支持事务操作的场景
实现的类
Command类:是一个接口,定义了执行操作和撤销操作的方法。
ConcreteCommand类:具体的执行命令,他们需要实现Command接口,负责执行具体的操作。
Invoker类:发送命令的对象。它并不知道具体的命令实现细节,只是将命令对象发送给接收者来执行。
Receiver类:真正执行命令的角色,那些具体的命令引用它,让它完成命令的执行。
实现:QT文本编辑器
Command类
// 命令接口
class Command : public QUndoCommand
{
public:
explicit Command(QTextEdit *editor) : m_editor(editor) {}
virtual void execute() = 0;
protected:
QTextEdit *m_editor;
};
Command 类是一个抽象类,继承自 QUndoCommand,定义了执行操作的接口方法 execute()。它提供了一个统一的接口,用于执行具体的操作。
ConcreteCommand类
// 具体的执行命令
class ConcreteCommand : public Command
{
public:
ConcreteCommand(QTextEdit *editor, const QString &text)
: Command(editor), m_text(text) {}
//执行数据插入
void execute() override
{
m_editor->textCursor().insertText(m_text);
}
private:
QString m_text;
};
ConcreteCommand 类继承自 Command 类,负责具体的操作逻辑。在这个例子中,ConcreteCommand 类的任务是在文本编辑器中插入指定的文本。
Receiver类
// 文本编辑器接收者
class Receiver : public QObject
{
Q_OBJECT
public:
Receiver(QTextEdit *editor) : m_editor(editor) {}
public slots:
void insertText(const QString &text)
{
m_editor->textCursor().insertText(text);
}
private:
QTextEdit *m_editor;
};
Receiver 类其中包含了一个槽函数 insertText,负责在文本编辑器中插入文本。Receiver 类接收并执行命令,实际执行操作的地方。
Invoker实现(QUndoStack)
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// 创建文本编辑器窗口
QTextEdit textEdit;
textEdit.setWindowTitle("Simple Text Editor");
textEdit.show();
// 创建撤销栈
QUndoStack undoStack;
// 创建文本编辑器接收者对象
Receiver receiver(&textEdit);
// 创建调用者对象并将其与撤销栈和接收者关联
QUndoCommand *insertCommand = new ConcreteCommand(&textEdit, "Hello, World!");
undoStack.push(insertCommand);
// 执行命令
undoStack.undo(); // 撤销插入操作
return app.exec();
}
在 main 函数中,我们创建了一个 QUndoStack 对象 undoStack,它充当了调用者的角色。QUndoStack 类提供了管理命令的功能,包括执行和撤销命令。我们将具体的命令对象添加到 undoStack 中,并调用 undo() 方法执行命令。因此,undoStack 是命令模式中的调用者,负责调度命令的执行和撤销。
原文链接:https://blog.csdn.net/buhuiCyvyan/article/details/138280467
————————————————
篇二、
撤销/重做框架(QUndoStack、QUndoCommand等类)
Qt的撤销/重做框架(QUndoStack、QUndoCommand等类)是命令模式的一种实现。在Qt的撤销/重做框架中,每个操作都被封装为一个QUndoCommand的子类对象。
以下是一个使用Qt的撤销/重做框架的程序示例:
#include <QApplication>
#include <QTextEdit>
#include <QUndoStack>
#include <QPushButton>
#include <QVBoxLayout>
#include <QUndoCommand>
class MyCommand : public QUndoCommand
{
public:
MyCommand(QTextEdit *editor, const QString &text, QUndoCommand *parent = nullptr)
: QUndoCommand(parent), m_editor(editor), m_text(text), m_oldText(editor->toPlainText()) {}
void undo() override
{
m_editor->setPlainText(m_oldText);
}
void redo() override
{
m_editor->setPlainText(m_text);
}
private:
QTextEdit *m_editor;
QString m_text;
QString m_oldText;
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QUndoStack stack;
QTextEdit editor;
QPushButton undoButton("Undo");
QPushButton redoButton("Redo");
QObject::connect(&undoButton, &QPushButton::clicked, &stack, &QUndoStack::undo);
QObject::connect(&redoButton, &QPushButton::clicked, &stack, &QUndoStack::redo);
QObject::connect(&editor, &QTextEdit::textChanged, [&]() {
stack.push(new MyCommand(&editor, editor.toPlainText()));
});
QVBoxLayout layout;
layout.addWidget(&editor);
layout.addWidget(&undoButton);
layout.addWidget(&redoButton);
QWidget window;
window.setLayout(&layout);
window.show();
return app.exec();
}
在上述示例中,MyCommand 是 QUndoCommand 的子类。每次 QTextEdit 的文本改变时,就会创建一个新的 MyCommand 对象并将其压入 QUndoStack。当点击 "Undo" 按钮时,就会撤销栈顶的命令;当点击 "Redo" 按钮时,就会重做栈顶的命令。
在这个例子中,相关的类扮演的角色如下:
命令(Command):QUndoCommand是命令接口。它定义了执行(redo)和撤销(undo)的方法。
具体命令(ConcreteCommand):MyCommand是具体的命令。它是 QUndoCommand的子类,实现了 redo和undo方法。
命令接收者(Receiver):QTextEdit是命令的接收者。它是执行命令的对象,MyCommand的 redo和 undo方法都是操作 QTextEdit。
命令发起者(Invoker):QUndoStack是命令的发起者。它负责调用和存储命令。QPushButton 实际上也扮演了命令发起者的角色,因为它们是触发执行或撤销命令的实际用户界面元素。
客户端(Client):在这个例子中,main 函数就是客户端。它创建了应用程序,包括命令接收者(QTextEdit)、命令发起者(QUndoStack 和 QPushButton)、并在 QTextEdit的文本变化时创建具体命令(MyCommand)。
下面给出相关的类在Qt源码中的实现。同样,这里隐去了很多与命令模式无关的细节,但对理解命令模式应该是足够的。
class QUndoCommand
{
public:
virtual ~QUndoCommand() {}
virtual void undo() = 0;
virtual void redo() = 0;
};
class QUndoStack
{
public:
void push(QUndoCommand *cmd)
{
m_stack.push(cmd);
cmd->redo();
}
void undo()
{
if (!m_stack.isEmpty()) {
QUndoCommand *cmd = m_stack.pop();
cmd->undo();
m_undoStack.push(cmd);
}
}
void redo()
{
if (!m_undoStack.isEmpty()) {
QUndoCommand *cmd = m_undoStack.pop();
cmd->redo();
m_stack.push(cmd);
}
}
private:
QStack<QUndoCommand*> m_stack;
QStack<QUndoCommand*> m_undoStack;
};
MyCommand类我们已经在前面实现了,这里不再重复。对于命令的接收者QTextEdit,我们也不需要关注它的源码,因为它的功能就是一个文本编辑器,我们只需要知道它提供了setPlainText()和toPlainText()等函数供我们在MyCommand中使用就可以了。
需要注意的是,在标准的命令模式中,通常只有一个存储命令对象的容器,可以是是队列或栈,也可以仅仅是只是单个命令对象(如我们一开始给出的命令模式的示例)。然而,在实现撤销/重做功能时,通常需要两个栈结构。
在Qt的QUndoStack中,主栈用于存储执行过的命令,当调用undo()方法时,会从主栈中弹出命令并执行其undo操作,同时该命令会被压入撤销栈。撤销栈用于存储撤销过的命令,当调用redo()方法时,会从撤销栈中弹出命令并执行其redo操作,同时该命令会被压回主栈。这样的设计使得QUndoStack能够按正确的顺序执行和撤销命令,同时还能在撤销命令后重新执行它们。