
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. 添加状态压缩:定期清理历史记录,只保留关键检查点。
通过备忘录模式,我们不仅实现了撤销功能,还让代码更加模块化和可维护。希望这个实战指南能帮助你在自己的项目中顺利实现撤销系统!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
源码库 » C++备忘录模式在图形编辑软件中的撤销实现
