最新公告
  • 欢迎您光临源码库,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入
  • C++备忘录模式在图形编辑软件中的撤销实现

    C++备忘录模式在图形编辑软件中的撤销实现插图

    C++备忘录模式在图形编辑软件中的撤销实现:从理论到实战的完整指南

    作为一名长期从事图形编辑软件开发的老兵,我深知撤销功能的重要性。记得有一次,用户误删了一个精心绘制了两小时的复杂图形,如果没有可靠的撤销系统,后果不堪设想。今天,我将分享如何使用C++备忘录模式构建健壮的撤销系统,这些经验都来自真实的项目实践。

    备忘录模式的核心思想

    备忘录模式的核心很简单:在不破坏封装性的前提下,捕获一个对象的内部状态,并在需要时恢复该状态。在图形编辑软件中,这意味着我们需要保存绘图对象在某个时间点的完整状态。

    想象一下,你正在开发一个绘图软件,用户绘制了圆形、矩形等图形,每个图形都有自己的位置、大小、颜色等属性。当用户执行撤销操作时,我们需要将画布恢复到之前的状态。

    基础类设计

    首先,我们定义三个核心类:Memento(备忘录)、Originator(原发器)和Caretaker(管理者)。

    
    // 备忘录类 - 负责存储图形状态
    class GraphicMemento {
    private:
        std::string state_;
        
    public:
        GraphicMemento(const std::string& state) : state_(state) {}
        std::string GetState() const { return state_; }
    };
    
    // 原发器类 - 图形对象
    class Graphic {
    private:
        std::string type_;    // 图形类型
        int x_, y_;           // 位置
        int width_, height_;  // 尺寸
        std::string color_;   // 颜色
    
    public:
        Graphic(const std::string& type, int x, int y, int w, int h, const std::string& color)
            : type_(type), x_(x), y_(y), width_(w), height_(h), color_(color) {}
        
        // 创建备忘录
        GraphicMemento* CreateMemento() {
            std::string state = type_ + "," + std::to_string(x_) + "," + 
                               std::to_string(y_) + "," + std::to_string(width_) + "," + 
                               std::to_string(height_) + "," + color_;
            return new GraphicMemento(state);
        }
        
        // 从备忘录恢复
        void RestoreFromMemento(GraphicMemento* memento) {
            std::string state = memento->GetState();
            // 解析状态字符串并恢复属性
            // 实际项目中建议使用更健壮的序列化方式
            std::vector tokens;
            std::string token;
            std::istringstream tokenStream(state);
            
            while (std::getline(tokenStream, token, ',')) {
                tokens.push_back(token);
            }
            
            if (tokens.size() >= 6) {
                type_ = tokens[0];
                x_ = std::stoi(tokens[1]);
                y_ = std::stoi(tokens[2]);
                width_ = std::stoi(tokens[3]);
                height_ = std::stoi(tokens[4]);
                color_ = tokens[5];
            }
        }
        
        void Display() const {
            std::cout << type_ << " at (" << x_ << "," << y_ << "), size: " 
                      << width_ << "x" << height_ << ", color: " << color_ << std::endl;
        }
        
        // 修改图形的方法
        void Move(int newX, int newY) {
            x_ = newX;
            y_ = newY;
        }
        
        void Resize(int newWidth, int newHeight) {
            width_ = newWidth;
            height_ = newHeight;
        }
    };
    

    管理者类的实现

    管理者类负责保存和管理备忘录对象,实现撤销和重做功能。在实际项目中,我建议使用智能指针来管理内存。

    
    // 管理者类 - 负责管理备忘录
    class Caretaker {
    private:
        std::vector> mementos_;
        size_t current_index_;
    
    public:
        Caretaker() : current_index_(-1) {}
        
        void SaveState(std::shared_ptr memento) {
            // 清除当前索引之后的所有状态(当执行新操作时)
            if (current_index_ + 1 < mementos_.size()) {
                mementos_.resize(current_index_ + 1);
            }
            mementos_.push_back(memento);
            current_index_ = mementos_.size() - 1;
            std::cout << "状态已保存,当前历史记录数: " << mementos_.size() << std::endl;
        }
        
        std::shared_ptr Undo() {
            if (current_index_ > 0) {
                current_index_--;
                std::cout << "撤销到状态 " << current_index_ << std::endl;
                return mementos_[current_index_];
            }
            std::cout << "无法撤销,已在最初状态" << std::endl;
            return nullptr;
        }
        
        std::shared_ptr Redo() {
            if (current_index_ + 1 < mementos_.size()) {
                current_index_++;
                std::cout << "重做到状态 " << current_index_ << std::endl;
                return mementos_[current_index_];
            }
            std::cout << "无法重做,已在最新状态" << std::endl;
            return nullptr;
        }
    };
    

    完整的图形编辑器实现

    现在让我们把这些组件组合成一个完整的图形编辑器。在实际项目中,我踩过一个坑:忘记在每次操作前保存状态,导致撤销功能不完整。

    
    class GraphicEditor {
    private:
        std::vector> graphics_;
        std::shared_ptr caretaker_;
    
    public:
        GraphicEditor() : caretaker_(std::make_shared()) {
            SaveState(); // 保存初始状态
        }
        
        void AddGraphic(const std::string& type, int x, int y, int w, int h, const std::string& color) {
            SaveState(); // 操作前保存状态
            auto graphic = std::make_shared(type, x, y, w, h, color);
            graphics_.push_back(graphic);
            std::cout << "添加图形: ";
            graphic->Display();
        }
        
        void MoveGraphic(int index, int newX, int newY) {
            if (index >= 0 && index < graphics_.size()) {
                SaveState(); // 操作前保存状态
                graphics_[index]->Move(newX, newY);
                std::cout << "移动图形 " << index << " 到 (" << newX << "," << newY << ")" << std::endl;
            }
        }
        
        void SaveState() {
            // 序列化所有图形状态
            std::string fullState;
            for (const auto& graphic : graphics_) {
                auto memento = graphic->CreateMemento();
                fullState += memento->GetState() + "|";
                delete memento; // 临时对象,需要删除
            }
            
            auto editorMemento = std::make_shared(fullState);
            caretaker_->SaveState(editorMemento);
        }
        
        void RestoreState(std::shared_ptr memento) {
            if (!memento) return;
            
            graphics_.clear();
            std::string fullState = memento->GetState();
            std::istringstream stateStream(fullState);
            std::string graphicState;
            
            while (std::getline(stateStream, graphicState, '|')) {
                if (!graphicState.empty()) {
                    auto tempMemento = std::make_shared(graphicState);
                    auto graphic = std::make_shared("", 0, 0, 0, 0, "");
                    graphic->RestoreFromMemento(tempMemento.get());
                    graphics_.push_back(graphic);
                }
            }
        }
        
        void Undo() {
            auto memento = caretaker_->Undo();
            RestoreState(memento);
            DisplayGraphics();
        }
        
        void Redo() {
            auto memento = caretaker_->Redo();
            RestoreState(memento);
            DisplayGraphics();
        }
        
        void DisplayGraphics() {
            std::cout << "n当前画布状态:" << std::endl;
            for (size_t i = 0; i < graphics_.size(); ++i) {
                std::cout << "图形 " << i << ": ";
                graphics_[i]->Display();
            }
            std::cout << std::endl;
        }
    };
    

    实战演示和踩坑提醒

    让我们看看这个系统如何工作:

    
    int main() {
        GraphicEditor editor;
        
        // 添加一些图形
        editor.AddGraphic("圆形", 10, 10, 50, 50, "红色");
        editor.AddGraphic("矩形", 100, 100, 80, 60, "蓝色");
        
        // 移动图形
        editor.MoveGraphic(0, 20, 20);
        
        // 测试撤销
        std::cout << "n--- 执行撤销 ---" << std::endl;
        editor.Undo();
        
        // 测试重做
        std::cout << "n--- 执行重做 ---" << std::endl;
        editor.Redo();
        
        return 0;
    }
    

    在实际开发中,我遇到过几个常见的坑:

    内存管理问题: 早期版本中我使用原始指针,导致内存泄漏。改用智能指针后问题得到解决。

    状态序列化: 简单的字符串拼接在复杂对象时不够用,后来我改用JSON或二进制序列化。

    性能优化: 当图形数量很多时,完整的状态保存会很慢。解决方案是使用增量保存或检查点机制。

    进阶优化建议

    对于生产环境,我建议:

    1. 使用命令模式结合备忘录模式:每个操作封装成命令对象,命令对象负责创建和恢复备忘录。

    2. 实现深拷贝和浅拷贝策略:对于大型对象,可以使用浅拷贝加变更记录的方式优化性能。

    3. 添加状态压缩:定期清理历史记录,只保留关键检查点。

    通过备忘录模式,我们不仅实现了撤销功能,还让代码更加模块化和可维护。希望这个实战指南能帮助你在自己的项目中顺利实现撤销系统!

    1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
    2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
    3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
    4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
    5. 如有链接无法下载、失效或广告,请联系管理员处理!
    6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!

    源码库 » C++备忘录模式在图形编辑软件中的撤销实现