美文网首页
Qt源码中的设计模式:撤销/重做框架与命令模式

Qt源码中的设计模式:撤销/重做框架与命令模式

作者: 饮茶先啦靓仔 | 来源:发表于2023-05-15 21:11 被阅读0次

命令模式

命令模式是一种行为设计模式,它将请求封装成一个对象,从而使我们可以将不同的请求、队列或日志请求等参数化,同时支持可撤销的操作。该模式的核心思想是将请求发送者和接收者解耦,让它们不直接交互,而是通过命令对象进行交互,即将请求封装成类对象。命令模式通常用于以下场景:

  1. 需要将请求发送者和接收者解耦的场景,以便于适应变化。

  2. 需要支持撤销和恢复操作的场景。

  3. 需要将一组操作组合在一起执行的场景,也称为批处理。

命令模式包含以下角色:

  1. 命令(Command):定义命令的接口,通常包含执行和撤销两个方法。

  2. 具体命令(ConcreteCommand):实现命令接口,包含了对应的操作。

  3. 命令接收者(Receiver):执行命令的对象。

  4. 命令发起者(Invoker):调用命令的对象,负责将命令发送给命令接收者。

  5. 客户端(Client):创建命令对象并将其发送给命令发起者。

命令模式UML类图

下面给出一个命令模式的C++示例:

class Command {
public:
    virtual ~Command() {}
    virtual void execute() = 0;
    virtual void undo() = 0;
};

class ConcreteCommand : public Command {
public:
    ConcreteCommand(std::shared_ptr<Receiver> receiver) : m_receiver(receiver) {}
    virtual void execute() {
        m_receiver->action();
    }
    virtual void undo() {
        m_receiver->undoAction();
    }
private:
    std::shared_ptr<Receiver> m_receiver;
};

class Receiver {
public:
    void action() {
        // 执行操作
    }
    void undoAction() {
        // 撤销操作
    }
};

class Invoker {
public:
    void setCommand(std::shared_ptr<Command> command) {
        m_command = command;
    }
    void executeCommand() {
        m_command->execute();
    }
    void undoCommand() {
        m_command->undo();
    }
private:
    std::shared_ptr<Command> m_command;
};

int main() {
    auto receiver = std::make_shared<Receiver>();
    auto command = std::make_shared<ConcreteCommand>(receiver);
    auto invoker = std::make_shared<Invoker>();
    invoker->setCommand(command);
    invoker->executeCommand();
    invoker->undoCommand();
    return 0;
}

在上面的示例中,Command类是命令接口,定义了execute和undo方法。ConcreteCommand类是具体命令类,实现了Command接口,包含了对应的操作。Receiver类是命令接收者,执行命令的对象。Invoker类是命令发起者,调用命令的对象,负责将命令发送给命令接收者。在客户端代码中,我们创建了一个Receiver对象和一个ConcreteCommand对象,并将其传递给Invoker对象。然后,我们调用Invoker对象的executeCommand方法来执行ConcreteCommand对象的操作。如果需要撤销操作,我们可以调用Invoker对象的undoCommand方法来执行ConcreteCommand对象的undo操作。

撤销/重做框架(QUndoStack、QUndoCommand等类)

Qt的撤销/重做框架(QUndoStackQUndoCommand等类)是命令模式的一种实现。在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();
}

在上述示例中,MyCommandQUndoCommand 的子类。每次 QTextEdit 的文本改变时,就会创建一个新的 MyCommand 对象并将其压入 QUndoStack。当点击 "Undo" 按钮时,就会撤销栈顶的命令;当点击 "Redo" 按钮时,就会重做栈顶的命令。

在这个例子中,相关的类扮演的角色如下:

  1. 命令(Command)QUndoCommand是命令接口。它定义了执行(redo)和撤销(undo)的方法。

  2. 具体命令(ConcreteCommand)MyCommand是具体的命令。它是 QUndoCommand的子类,实现了 redoundo方法。

  3. 命令接收者(Receiver)QTextEdit是命令的接收者。它是执行命令的对象,MyCommandredoundo方法都是操作 QTextEdit

  4. 命令发起者(Invoker)QUndoStack是命令的发起者。它负责调用和存储命令。QPushButton 实际上也扮演了命令发起者的角色,因为它们是触发执行或撤销命令的实际用户界面元素。

  5. 客户端(Client):在这个例子中,main 函数就是客户端。它创建了应用程序,包括命令接收者(QTextEdit)、命令发起者(QUndoStackQPushButton)、并在 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能够按正确的顺序执行和撤销命令,同时还能在撤销命令后重新执行它们。

总结

Qt的撤销/重做框架,实现了命令模式,并使用了两个栈的方式维护了操作的历史记录,确实是很精妙的设计。除此之外,Qt的撤销/重做框架是支持多个命令的合并的,这在文字编辑或者其他需要撤销/重做框架的需求中,都是很有用的。QUndoCommand类提供了一个可重写的mergeWith方法,可以用来合并连续的、类似的操作,使其在撤销/重做时被视为一个单一的操作。这里由于篇幅问题,不展开讨论。总的来说,Qt的撤销/重做框架,很好地实现了命令模式。

相关文章

  • 自己动手设计代码编辑器——(三)撤销与重做

    谈到代码编辑器,基本功能的“撤销与重做”是必不可少的。刚好最近看了设计模式的“命令模式”,做这个倒是正好简单来说,...

  • 4-2 vim 的使用

    启动vim 文件命令 vim的模式 导航命令 插入命令 查找命令 替换命令 移动命令 撤销和重做 删除命令 拷贝和...

  • vim 命令

    启动vim vim的模式 导航命令 插入命令 查找命令 替换命令 移动命令 撤销和重做 删除命令 拷贝和粘贴 剪切...

  • 编程模式·命令模式

    将函数调用封装成命令对象,从而支持各种后续管理:传递,保存,延后处理,撤销重做等 经典命令模式解析 命令模式的5个...

  • Qt网络编程的命令模式:把网络命令封装成类

    命令模式是编程设计模式中的一种,这里介绍命令模式在Qt网络编程中的使用,讲述如何实现“一个类就是一个命令”的设计思...

  • 常用vim命令

    vim存在3中模式,分别是命令模式、输入模式和编辑模式。 光标跳转 命令模式 编辑模式 复制粘贴 撤销

  • 设计模式详解——适配器模式

    本篇文章介绍一种设计模式——命令模式。本篇文章内容参考《JAVA与模式》之适配器模式,Android设计模式源码解...

  • 命令模式

    摘自《JavaScript设计模式与开发实践》 命令模式是最简单和优雅的模式之一,命令模式中的命令(command...

  • 软件设计混淆概念书目录

    设计概念 设计模式 框架 架构 平台 框架与架构之间的关系 框架与设计模式之间的关系

  • Vi编辑器

    命令模式 文本输入模式 末行模式。 插入模式 移动光标: 删除命令: 撤销命令: 重复命令 文本行移动: 复制粘贴...

网友评论

      本文标题:Qt源码中的设计模式:撤销/重做框架与命令模式

      本文链接:https://www.haomeiwen.com/subject/enhvsdtx.html