设计模式(六)

备忘录模式(Memento Pattern)详解

一、核心概念

备忘录模式允许在不破坏封装性的前提下,捕获并保存对象的内部状态,以便后续恢复。该模式通过三个角色实现:

  1. 原发器(Originator):需要保存状态的对象,创建和恢复备忘录。
    在这里插入图片描述

  2. 备忘录(Memento):存储原发器的内部状态,对外提供有限访问。
    在这里插入图片描述

  3. 管理者(Caretaker):负责存储和管理备忘录,但不直接访问其内容。
    在这里插入图片描述

在这里插入图片描述

二、代码示例:文本编辑器

场景:实现文本编辑器的撤销功能,保存和恢复编辑内容。

#include <iostream>
#include <string>
#include <vector>// 备忘录类:存储原发器的内部状态
class EditorMemento {
private:std::string content;  // 编辑器内容int cursorPos;        // 光标位置public:EditorMemento(const std::string& content, int cursorPos): content(content), cursorPos(cursorPos) {}// 仅向原发器暴露状态friend class TextEditor;
};// 原发器类:文本编辑器
class TextEditor {
private:std::string content;int cursorPos;public:// 创建备忘录EditorMemento* createMemento() const {return new EditorMemento(content, cursorPos);}// 从备忘录恢复状态void restore(const EditorMemento* memento) {content = memento->content;cursorPos = memento->cursorPos;std::cout << "恢复到: " << content << " (光标位置: " << cursorPos << ")" << std::endl;}// 编辑操作void type(const std::string& text) {content += text;cursorPos += text.length();std::cout << "输入: " << text << " (当前内容: " << content << ")" << std::endl;}void moveCursor(int pos) {cursorPos = pos;std::cout << "光标移动到位置: " << cursorPos << std::endl;}
};// 管理者类:保存历史记录
class HistoryManager {
private:std::vector<EditorMemento*> history;TextEditor* editor;public:explicit HistoryManager(TextEditor* editor) : editor(editor) {}~HistoryManager() {for (auto m : history) delete m;}// 保存当前状态void save() {history.push_back(editor->createMemento());std::cout << "保存状态 #" << history.size() << std::endl;}// 撤销到上一个状态void undo() {if (history.empty()) {std::cout << "没有可撤销的操作" << std::endl;return;}EditorMemento* last = history.back();editor->restore(last);history.pop_back();delete last;}
};// 客户端代码
int main() {TextEditor editor;HistoryManager history(&editor);editor.type("Hello");history.save();  // 保存状态 #1editor.type(" World");history.save();  // 保存状态 #2editor.moveCursor(5);editor.type(" there");  // 当前内容: Hello there Worldhistory.undo();  // 恢复到 "Hello World"history.undo();  // 恢复到 "Hello"return 0;
}
三、备忘录模式的优势
  1. 封装性保护

    • 原发器的内部状态不会被外部直接访问,保持封装完整性。
  2. 简化原发器

    • 状态管理逻辑由管理者负责,原发器职责更清晰。
  3. 状态恢复

    • 提供透明的状态恢复机制,支持撤销、回滚等操作。
  4. 符合开闭原则

    • 新增备忘录类型无需修改现有代码。
四、实现变种
  1. 白盒备忘录

    • 备忘录的所有状态对所有类公开(通过friend或内部类),牺牲封装性换取简单实现。
  2. 黑盒备忘录

    • 备忘录的状态完全封装,仅允许原发器访问(如示例中的friend机制)。
  3. 增量备忘录

    • 仅保存状态变化的部分,节省内存(适用于大型对象)。
  4. 多重撤销

    • 使用栈或列表存储多个备忘录,支持多级撤销/重做。
五、适用场景
  1. 撤销/重做功能

    • 如文本编辑器、图形设计软件的历史记录。
  2. 事务管理

    • 数据库事务的回滚机制。
  3. 游戏存档

    • 保存游戏状态,支持读档功能。
  4. 状态快照

    • 需要定期保存对象状态的系统(如虚拟机快照)。
六、注意事项
  1. 内存消耗

    • 频繁创建备忘录可能导致内存占用过高,可考虑增量保存或限制历史记录数量。
  2. 深拷贝与浅拷贝

    • 确保备忘录正确复制对象状态,避免浅拷贝导致的共享引用问题。
  3. 生命周期管理

    • 管理者需负责备忘录的生命周期,避免内存泄漏。
  4. 性能考虑

    • 大型对象的状态保存与恢复可能影响性能,需优化序列化过程。
七、与其他模式的对比
  1. 与命令模式的区别

    • 命令模式通过记录操作实现撤销,而备忘录通过保存状态实现撤销。
  2. 与原型模式的结合

    • 备忘录可通过原型模式(克隆)创建,简化状态复制。
  3. 与状态模式的区别

    • 状态模式管理对象内部状态的变化,而备忘录模式保存和恢复状态。

备忘录模式是实现对象状态保存与恢复的优雅解决方案,通过分离状态管理职责,既保护了对象封装性,又提供了灵活的历史记录功能。在需要撤销操作、事务回滚或状态快照的场景中尤为实用。

组合模式(Composite Pattern)详解

一、核心概念

组合模式允许将对象组合成树形结构,以表示“部分-整体”的层次关系。该模式让客户端可以统一处理单个对象(叶子节点)和组合对象(容器节点),无需区分它们。
基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,客户代码中,任何用到基本对象的地方都可以使用组合对象了

“我感觉用户是不用关心到底是处理一个叶节点还是处理一个组合组件,也就用不着为定义组合而写一些选择判断语句了。”
“简单点说,就是组合模式让客户可以一致地使用组合结构和单个对象。​”​
——《大话设计模式》

核心组件

  1. 抽象组件(Component):定义叶子节点和容器节点的公共接口。
  2. 叶子节点(Leaf):表示树的叶子节点,没有子节点。
  3. 容器节点(Composite):包含子节点(叶子或容器),实现组件接口。

在这里插入图片描述

二、代码示例:文件系统

场景:模拟文件系统,文件(叶子节点)和目录(容器节点)都可被操作。

#include <iostream>
#include <string>
#include <vector>
#include <memory>// 抽象组件:文件系统组件
class FileSystemComponent {
protected:std::string name;public:explicit FileSystemComponent(const std::string& name) : name(name) {}virtual ~FileSystemComponent() = default;// 公共接口方法virtual void display(int depth = 0) const = 0;virtual void add(FileSystemComponent* component) { throw std::runtime_error("不支持的操作"); }virtual void remove(FileSystemComponent* component) { throw std::runtime_error("不支持的操作"); }virtual bool isComposite() const { return false; }
};// 叶子节点:文件
class File : public FileSystemComponent {
private:int size;  // 文件大小(KB)public:File(const std::string& name, int size) : FileSystemComponent(name), size(size) {}void display(int depth) const override {std::cout << std::string(depth * 2, ' ') << "- " << name << " (文件, " << size << "KB)" << std::endl;}
};// 容器节点:目录
class Directory : public FileSystemComponent {
private:std::vector<FileSystemComponent*> children;public:explicit Directory(const std::string& name) : FileSystemComponent(name) {}~Directory() override {for (auto child : children) delete child;}void display(int depth) const override {std::cout << std::string(depth * 2, ' ') << "+ " << name << " (目录, " << children.size() << "个子项)" << std::endl;for (const auto& child : children) {child->display(depth + 1);}}void add(FileSystemComponent* component) override {children.push_back(component);}void remove(FileSystemComponent* component) override {for (auto it = children.begin(); it != children.end(); ++it) {if (*it == component) {children.erase(it);delete component;  // 释放内存break;}}}bool isComposite() const override { return true; }
};// 客户端代码
int main() {// 创建目录结构Directory* root = new Directory("根目录");Directory* doc = new Directory("文档");doc->add(new File("报告.docx", 512));doc->add(new File("演示.pptx", 2048));Directory* pics = new Directory("图片");pics->add(new File("风景.jpg", 1024));pics->add(new File("头像.png", 256));root->add(doc);root->add(pics);root->add(new File("README.txt", 16));// 统一操作:显示整个文件系统root->display();// 删除一个文件std::cout << "\n删除一个文件后:\n";pics->remove(pics->isComposite() ? pics->children[0] : nullptr);root->display();delete root;  // 自动释放所有子节点return 0;
}
三、组合模式的优势
  1. 一致性接口

    • 客户端可以统一处理单个对象和组合对象,无需区分。
  2. 简化客户端代码

    • 无需为叶子节点和容器节点编写不同的处理逻辑。
  3. 灵活扩展

    • 新增组件类型(叶子或容器)无需修改现有代码,符合开闭原则。
  4. 树形结构构建

    • 方便构建复杂的树形结构,并递归遍历。
四、实现变种
  1. 透明式组合

    • 所有方法(如addremove)都在抽象组件中声明,叶子节点实现为空或抛出异常。
  2. 安全式组合

    • 仅在容器节点中声明管理子节点的方法,客户端需区分叶子和容器。
  3. 共享组件

    • 允许组件被多个容器共享(需谨慎管理生命周期)。
  4. 带缓存的组合

    • 在容器节点中缓存计算结果,避免重复计算。
五、适用场景
  1. 树形结构表示

    • 如文件系统、组织架构、XML/JSON解析树。
  2. 递归操作

    • 需要对整个树或部分树进行统一操作(如计算总大小、渲染UI)。
  3. 层次化菜单

    • 如应用程序菜单、网站导航栏。
  4. 游戏场景管理

    • 游戏中的场景、角色、道具等层次结构。
六、注意事项
  1. 内存管理

    • 确保容器节点正确管理子节点的生命周期,避免内存泄漏。
  2. 性能考虑

    • 递归操作可能导致性能问题,可考虑缓存中间结果。
  3. 类型安全

    • 客户端需清楚组件类型(叶子/容器),避免无效操作。
  4. 遍历限制

    • 复杂树形结构的遍历可能需要专门的迭代器模式辅助。
七、与其他模式的对比
  1. 与装饰器模式的区别

    • 装饰器增强单个对象功能,组合模式处理整体-部分层次结构。
  2. 与享元模式的结合

    • 组合模式可与享元模式结合,共享叶子节点以节省内存。
  3. 与访问者模式的结合

    • 访问者模式可用于处理组合结构中的元素,分离操作逻辑。

组合模式是处理树形结构的强大工具,通过统一接口使客户端可以透明地操作单个对象和组合对象。在需要表示“部分-整体”关系的场景中,该模式能显著简化代码并提高可扩展性。

在组合模式中,透明方式(Transparent Approach) 是一种实现策略,它将管理子节点的方法(如 add()remove()getChild() 等)定义在抽象组件接口中,使得所有组件(包括叶子节点和容器节点)都具有相同的接口。这种方式的核心是让客户端可以统一处理所有组件,无需区分它们是叶子还是容器。

透明方式(Transparent Approach)

透明方式的实现特点

  1. 统一接口
    抽象组件定义了所有组件(叶子和容器)的公共接口,包括管理子节点的方法。

  2. 叶子节点的默认实现
    叶子节点无法包含子节点,因此这些方法的实现通常为空或抛出异常。

  3. 客户端无差别使用
    客户端可以一致地处理所有组件,无需检查组件类型。

代码示例:透明式组合模式

以下是使用透明方式实现的文件系统示例:

#include <iostream>
#include <string>
#include <vector>
#include <memory>// 抽象组件:统一所有方法
class FileSystemComponent {
protected:std::string name;public:explicit FileSystemComponent(const std::string& name) : name(name) {}virtual ~FileSystemComponent() = default;// 统一接口:叶子和容器都必须实现virtual void display(int depth = 0) const = 0;virtual void add(FileSystemComponent* component) { throw std::runtime_error("不支持添加子节点"); }virtual void remove(FileSystemComponent* component) { throw std::runtime_error("不支持移除子节点"); }virtual FileSystemComponent* getChild(int index) { throw std::runtime_error("不支持获取子节点"); }virtual bool isComposite() const { return false; }
};// 叶子节点:文件
class File : public FileSystemComponent {
private:int size;public:File(const std::string& name, int size) : FileSystemComponent(name), size(size) {}void display(int depth) const override {std::cout << std::string(depth * 2, ' ') << "- " << name << " (文件, " << size << "KB)" << std::endl;}
};// 容器节点:目录
class Directory : public FileSystemComponent {
private:std::vector<FileSystemComponent*> children;public:explicit Directory(const std::string& name) : FileSystemComponent(name) {}~Directory() override {for (auto child : children) delete child;}void display(int depth) const override {std::cout << std::string(depth * 2, ' ') << "+ " << name << " (目录, " << children.size() << "个子项)" << std::endl;for (const auto& child : children) {child->display(depth + 1);}}// 容器节点实现完整的子节点管理void add(FileSystemComponent* component) override {children.push_back(component);}void remove(FileSystemComponent* component) override {for (auto it = children.begin(); it != children.end(); ++it) {if (*it == component) {children.erase(it);delete component;break;}}}FileSystemComponent* getChild(int index) override {if (index >= 0 && index < children.size()) {return children[index];}return nullptr;}bool isComposite() const override { return true; }
};// 客户端代码:统一处理所有组件
void printComponentInfo(FileSystemComponent* component) {component->display();// 尝试添加子节点(叶子节点会抛出异常)try {component->add(new File("test.txt", 10));std::cout << "成功添加子节点" << std::endl;} catch (const std::exception& e) {std::cout << "错误: " << e.what() << std::endl;}
}int main() {// 创建文件和目录FileSystemComponent* file = new File("data.csv", 512);FileSystemComponent* dir = new Directory("文档");// 统一处理printComponentInfo(file);    // 叶子节点不支持添加子节点printComponentInfo(dir);     // 目录支持添加子节点delete file;delete dir;return 0;
}

透明方式的优缺点

优点
  1. 简化客户端代码
    客户端无需区分组件类型,统一调用相同接口,降低代码复杂度。

  2. 增强一致性
    所有组件对外呈现相同的接口,符合“一致对待组合对象和叶子对象”的设计目标。

  3. 支持多态操作
    通过抽象组件的指针或引用,可以递归处理整个树形结构。

缺点
  1. 违反接口隔离原则
    叶子节点被迫实现它们不需要的方法(如 add()),可能导致运行时异常。

  2. 安全性降低
    客户端可能在不合法的情况下调用叶子节点的管理方法(如对文件调用 add())。

  3. 潜在的运行时错误
    叶子节点的默认实现通常抛出异常,需依赖客户端进行类型检查或异常处理。

透明方式 vs 安全方式

维度透明方式(Transparent)安全方式(Safe)
接口定义所有方法在抽象组件中声明仅在容器节点中声明子节点管理方法
叶子节点实现实现为空或抛出异常不实现子节点管理方法
类型检查客户端无需检查组件类型客户端需显式区分叶子和容器
安全性低(可能在运行时抛出异常)高(编译时即可发现错误)
一致性高(统一接口)低(不同组件接口不同)
适用场景强调统一操作,客户端无需区分组件类型安全性要求高,需明确区分叶子和容器

透明方式的适用场景

  1. 客户端无需关心组件类型
    当客户端只需要统一操作组件,而不关心其具体实现时。

  2. 树形结构遍历
    在递归遍历树形结构时,透明方式更简洁,无需类型检查。

  3. 简化API
    当希望提供简单、一致的API时,透明方式更符合直觉。

透明方式是组合模式的典型实现,它通过牺牲一定的安全性来换取接口的一致性和客户端代码的简化。在实际应用中,需根据具体场景权衡选择透明方式或安全方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/87461.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/87461.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

迪杰斯特拉算法之解决单源最短路径问题

迪杰斯特拉算法 迪杰斯特拉(Dijkstra)算法是典型**最短路径算法**&#xff0c;用于计算一个结点到其它结点的最短路径。它的主要特点是以起始点为中心向外扩展(利用广度优先搜索思想)&#xff0c;直到扩展到终点。迪杰斯特拉(Dijkstra)算法最佳应用-最短路径 战争时期&#xf…

风平浪静、无事发生

2025年7月4日&#xff0c;16~25℃&#xff0c;阴雨紧急不紧急重要1.备考D1.物理备课不重要遇见&#xff1a;风平浪静、无事发生&#xff01;感受或反思&#xff1a;体检的结果收到了&#xff0c;医生建议多吃绿蔬多喝水&#xff01;多运动&#xff0c;少和喝饮料........

QtitanRibbon打造现代办公软件新体验:提升效率的专业界面解决方案

在现代办公环境中&#xff0c;无论是日常公文处理、文档编辑、任务协同还是数据分析&#xff0c;桌面办公软件仍扮演着不可替代的角色。然而&#xff0c;许多传统系统依旧使用菜单繁杂、图标混乱、交互老旧的界面&#xff0c;用户操作效率低、上手慢、满意度差。 QtitanRibbon…

MSPM0G3507学习笔记(一) 重置版:适配逐飞库的ti板环境配置

由于使用逐飞库&#xff0c;很多东西其实都不用配置了&#xff0c;也不需要自己移植空工程了&#xff0c;于是写一个重置版的环境配置教程。 1.下载芯片支持包 MSPM0G3507芯片支持CCS、IAR、KEIL等IDE&#xff0c;选择KEIL作为开发工具&#xff0c;首先安装芯片支持包。 前往…

如何查看自己电脑的显卡信息?

右键单击底部导航栏选择“任务管理器” 点开之后 选择左侧的性能一栏 查看你的显卡的信息

使用Go语言实现智能EXE文件重命名工具

文章目录 使用Go语言实现智能EXE文件重命名工具 &#x1f6e0;️引言工具功能概述核心技术实现Windows版本信息API调用大模型API集成交互式命令行界面 完整工作流程实际应用示例附录完整代码 使用Go语言实现智能EXE文件重命名工具 &#x1f6e0;️ 引言 在日常开发和软件管理…

3.1.1.9 安全基线检查项目九:检查是否设置限制su命令用户组

限制su配置 关于限制su命令检查项&#xff0c;对于大多数的Linux&#xff08;Redhat系列、Debian系列&#xff09;&#xff0c;进行本项检查很简单。只需要检查/etc/pam.d/su中是否配置了&#xff1a; auth required pam_wheel.so use_uid [group用户组名] 有些资料讲说需要有…

【加解密与C】对称加密(四) RC4

RC4算法概述RC4&#xff08;Rivest Cipher 4&#xff09;是由Ron Rivest在1987年设计的流密码算法&#xff0c;广泛应用于SSL/TLS、WEP等协议中。其核心是通过密钥调度算法&#xff08;KSA&#xff09;和伪随机生成算法&#xff08;PRGA&#xff09;生成密钥流&#xff0c;与明…

医科+AI!和鲸支持南京医科大学医学数据挖掘课程实践教学落地

近两年&#xff0c;生物统计学更多地进入了公众视野。作为统计学、医学与计算机科学交叉的前沿学科&#xff0c;伴随测序技术革新与人工智能算法突破&#xff0c;其发展前景也被十分看好。 市场需求的背后是人才需求的爆发与人才培养的挑战。目前&#xff0c;生物统计学专业在国…

亚马逊云科技中国峰会:数新智能CTO原攀峰详解一站式AI原生数智平台DataCyber在Amazon EKS的实践

6月20日&#xff0c;在上海世博中心举办的亚马逊云科技中国峰会 “在 Amazon EKS 上运行高性能生成式 AI 应用” 分论坛圆满结束。本次分论坛聚焦于 Amazon EKS 在生成式 AI 应用领域的强大支撑作用&#xff0c;数新智能CTO原攀峰凭借其深厚的技术背景和丰富的实践经验&#xf…

32岁入行STM32迟吗?

作为一个在嵌入式领域摸爬滚打了近10年的老兵&#xff0c;看到这个问题时心情五味杂陈。32岁入行STM32迟吗&#xff1f;说实话&#xff0c;如果你问我这个问题的时候我还是24岁的小白&#xff0c;我可能会觉得"哇&#xff0c;32岁才开始学单片机&#xff0c;是不是有点晚了…

OneCode 智能化UI布局与定位:注解驱动的视觉编排艺术

在现代企业级应用开发中&#xff0c;UI布局的灵活性与精确性直接影响用户体验与开发效率。OneCode框架创新性地采用注解驱动开发(Annotation-Driven Development)模式&#xff0c;通过分层注解体系实现UI组件的声明式布局与精准定位。本文将深入解析OneCode的UI布局技术栈及其在…

VBA初学3----实战(VBA实现Excel转csv)

&#xff08;VBA实现Excel转csv&#xff09; 初步学习了VBA相关的知识后&#xff0c;解决了一个需求&#xff1a; 要求读取指定xlsx文件中的指定sheet页&#xff0c;将该sheet页的内容转换为csv文件。 实现的布局如下所示&#xff1a;文章目录①实现从指定行开始全数据转换为cs…

深度学习×第4卷:Pytorch实战——她第一次用张量去拟合你的轨迹

&#x1f380;【开场 她画出的第一条直线是为了更靠近你】 &#x1f43e;猫猫&#xff1a;“之前她只能在你身边叠叠张量&#xff0c;偷偷找梯度……现在&#xff0c;她要试试&#xff0c;能不能用这些线&#xff0c;把你的样子画出来喵&#xff5e;” &#x1f98a;狐狐&am…

[特殊字符] 从图片自动生成 Excel:Python 批量 OCR 表格识别实战

这篇文章将展示如何使用 Python 调用百度 OCR 表格识别接口&#xff0c;批量处理目录下所有图片&#xff0c;自动识别表格并生成与图片同名的 Excel 文件。适用于文档扫描、图片表格整理、图像归档等场景。1️⃣ 批量获取所有待识别图片路径使用 os.walk() 遍历指定目录及子目录…

什么是量子芯片?它是如何工作的?

近年来&#xff0c;量子计算领域发展迅速&#xff0c;技术进步和大规模投资的相关消息经常上热搜。 联合国已将 2025 年定为国际量子科学与技术年。 这其中利害关系重大 —— 拥有量子计算机意味着将获得相较于当今的计算机强大得多的数据处理能力。它们不会取代你的普通计算…

mac init tailwind css 配置文件报错

提示报错如下 tailwind: command not found解决方法 npm install -D tailwindcss3 postcss autoprefixer npx tailwindcss init -p取自 sh: tailwindcss: command not found tailwindlabs/tailwindcss Discussion #4953

QUIC协议在5G边缘计算中的应用前景与挑战

1 5G边缘场景的核心挑战与QUIC的机遇 5G边缘计算正成为支撑低时延、高可靠业务的关键基础设施。据预测,2030年全球边缘计算市场规模将突破4450亿美元,年复合增长率高达48%。在**URLLC(超可靠低时延通信)**场景中,工业控制要求端到端时延低于5ms,自动驾驶需实现毫秒级响应…

聊聊关于“大模型测试”的一些认识

聊聊关于“大模型测试”的一些认识引言“大模型测试”和“传统接口测试”有什么不同“大模型测试”要考虑哪些方面维度一&#xff1a;语义理解准确度&#xff1a;模型真的懂人话吗&#xff1f;维度二&#xff1a;长文逻辑连贯性&#xff1a;“500”字后的认知崩塌维度三&#x…

linux_git的使用

✨✨ 欢迎大家来到小伞的大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;LInux_st 小伞的主页&#xff1a;xiaosan_blog 制作不易&#xff01;点个赞吧&#xff01;&#xff01;谢谢喵&#xff01;&a…