当前位置: 首页 > news >正文

设计模式(十)

享元模式(Flyweight Pattern)详解

一、核心概念

享元模式通过共享技术复用相同或相似的细粒度对象,以减少内存占用和提高性能。该模式将对象状态分为内部状态(可共享的不变部分)和外部状态(需外部传入的可变部分),通过共享内部状态降低对象数量。

核心组件

  1. 抽象享元(Flyweight):定义共享对象的接口,声明处理外部状态的方法。
  2. 具体享元(Concrete Flyweight):实现抽象享元接口,包含内部状态并处理外部状态。
  3. 享元工厂(Flyweight Factory):创建和管理享元对象,确保合理共享。
  4. 客户端(Client):通过工厂获取享元对象,并传入外部状态。

在享元对象内部并且不会随环境改变而改变的共享部分,可以称为是享元对象的内部状态,而随环境改变而改变的、不可以共享的状态就是外部状态了。事实上,享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够受大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。也就是说,享元模式Flyweight执行时所需的状态是有内部的也可能有外部的,内部状态存储于ConcreteFlyweight对象之中,而外部对象则应该考虑由客户端对象存储或计算,当调用Flyweight对象的操作时,将该状态传递给它。​” ——《大话设计模式》

“大鸟,你通过这个例子来讲解享元模式虽然我是理解了,但在现实中什么时候才应该考虑使用享元模式呢?​”
“就知道你会问这样的问题,如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。​”

二、代码示例:文本编辑器中的字符共享

场景:文本编辑器中,每个字符的字体、颜色等属性可共享,位置和内容为外部状态。

#include <iostream>
#include <string>
#include <memory>
#include <unordered_map>// 外部状态:字符位置
struct CharacterPosition {int x, y;
};// 抽象享元:字符
class Character {
protected:char symbol;       // 内部状态:字符符号std::string font;  // 内部状态:字体int size;          // 内部状态:字号public:Character(char symbol, const std::string& font, int size) : symbol(symbol), font(font), size(size) {}virtual void display(const CharacterPosition& pos) = 0;virtual ~Character() = default;
};// 具体享元:ASCII字符
class AsciiCharacter : public Character {
public:using Character::Character;  // 继承构造函数void display(const CharacterPosition& pos) override {std::cout << "字符: " << symbol << ", 字体: " << font << ", 字号: " << size << ", 位置: (" << pos.x << ", " << pos.y << ")" << std::endl;}
};// 享元工厂:字符工厂
class CharacterFactory {
private:std::unordered_map<std::string, std::shared_ptr<Character>> characters;public:std::shared_ptr<Character> getCharacter(char symbol, const std::string& font, int size) {std::string key = std::string(1, symbol) + "-" + font + "-" + std::to_string(size);if (characters.find(key) == characters.end()) {// 若不存在,则创建新的享元对象characters[key] = std::make_shared<AsciiCharacter>(symbol, font, size);std::cout << "创建新字符: " << key << std::endl;} else {std::cout << "复用已存在字符: " << key << std::endl;}return characters[key];}size_t getCharacterCount() const {return characters.size();}
};// 客户端代码
int main() {CharacterFactory factory;// 创建并显示多个字符(部分共享)factory.getCharacter('A', "Arial", 12)->display({10, 20});factory.getCharacter('B', "Arial", 12)->display({30, 20});factory.getCharacter('A', "Arial", 12)->display({50, 20});  // 复用'A'factory.getCharacter('A', "Times New Roman", 14)->display({70, 20});  // 新字符std::cout << "总共创建的字符对象数: " << factory.getCharacterCount() << std::endl;return 0;
}
三、享元模式的优势
  1. 减少内存占用

    • 通过共享相同内部状态的对象,大幅降低内存消耗。
  2. 提高性能

    • 减少对象创建和垃圾回收的开销。
  3. 集中管理状态

    • 内部状态由享元对象统一维护,外部状态由客户端管理。
  4. 符合开闭原则

    • 新增享元类型无需修改现有代码,易于扩展。
四、实现变种
  1. 单纯享元 vs 复合享元

    • 复合享元将多个单纯享元组合成更复杂的对象,仍保持共享特性。
  2. 静态工厂 vs 动态工厂

    • 静态工厂在初始化时创建所有享元;动态工厂按需创建。
  3. 弱引用缓存

    • 使用std::weak_ptr管理享元对象,允许不再使用的对象被垃圾回收。
五、适用场景
  1. 系统中存在大量相似对象

    • 如游戏中的粒子效果、文本处理中的字符。
  2. 对象状态可分为内部/外部状态

    • 内部状态可共享,外部状态需动态传入。
  3. 对象创建成本高

    • 如数据库连接、网络连接等资源密集型对象。
  4. 需要缓存对象

    • 如缓存配置信息、模板对象等。
六、注意事项
  1. 状态划分复杂性

    • 正确区分内部状态和外部状态需要仔细设计,避免逻辑混乱。
  2. 线程安全

    • 共享对象可能被多线程访问,需确保线程安全。
  3. 性能权衡

    • 享元模式引入工厂和缓存逻辑,需权衡其带来的额外开销。
  4. 与其他模式结合

    • 常与工厂模式结合创建享元对象,与组合模式构建复合享元。

享元模式通过共享技术优化大量细粒度对象的内存占用,是一种典型的“以时间换空间”的优化策略。在需要处理大量相似对象的场景中,该模式能显著提升系统性能。

解释器模式(Interpreter Pattern)详解

一、核心概念

解释器模式用于定义语言的文法表示,并创建解释器来解析该语言中的句子。它将语言中的每个语法规则映射为一个类,使语法规则的实现和使用分离,适合简单的领域特定语言(DSL)。

核心组件

  1. 抽象表达式(Abstract Expression):定义解释操作的接口。
  2. 终结符表达式(Terminal Expression):实现与文法中的终结符相关的解释操作(如变量、常量)。
  3. 非终结符表达式(Non-terminal Expression):实现文法中的非终结符操作(如运算符、语句结构)。
  4. 上下文(Context):包含解释器需要的全局信息。
  5. 客户端(Client):构建抽象语法树并调用解释器。

“解释器模式有什么好处呢?​”
“用了解释器模式,就意味着可以很容易地改变和扩展文法,因为该模式使用类来表示文法规则,你可使用继承来改变或扩展该文法。也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接编写[DP]​。​”

二、代码示例:简单的布尔表达式解释器

场景:实现一个简单的布尔表达式解释器,支持变量(如xy)、逻辑与(AND)、逻辑非(NOT)。

#include <iostream>
#include <string>
#include <memory>
#include <unordered_map>
#include <vector>// 上下文:存储变量值
class Context {
private:std::unordered_map<std::string, bool> variables;public:void setVariable(const std::string& name, bool value) {variables[name] = value;}bool getVariable(const std::string& name) const {auto it = variables.find(name);return it != variables.end() ? it->second : false;}
};// 抽象表达式
class Expression {
public:virtual bool interpret(const Context& context) const = 0;virtual ~Expression() = default;
};// 终结符表达式:变量
class VariableExpression : public Expression {
private:std::string name;public:explicit VariableExpression(const std::string& name) : name(name) {}bool interpret(const Context& context) const override {return context.getVariable(name);}
};// 终结符表达式:常量
class ConstantExpression : public Expression {
private:bool value;public:explicit ConstantExpression(bool value) : value(value) {}bool interpret(const Context& context) const override {return value;}
};// 非终结符表达式:逻辑与
class AndExpression : public Expression {
private:std::shared_ptr<Expression> left;std::shared_ptr<Expression> right;public:AndExpression(std::shared_ptr<Expression> left, std::shared_ptr<Expression> right): left(left), right(right) {}bool interpret(const Context& context) const override {return left->interpret(context) && right->interpret(context);}
};// 非终结符表达式:逻辑非
class NotExpression : public Expression {
private:std::shared_ptr<Expression> operand;public:explicit NotExpression(std::shared_ptr<Expression> operand) : operand(operand) {}bool interpret(const Context& context) const override {return !operand->interpret(context);}
};// 表达式解析器(简化版)
class Parser {
public:static std::shared_ptr<Expression> parse(const std::string& input) {// 实际应用中需实现完整的词法和语法分析// 此处简化为直接构建表达式if (input == "true") {return std::make_shared<ConstantExpression>(true);} else if (input == "false") {return std::make_shared<ConstantExpression>(false);} else if (input == "NOT x") {auto x = std::make_shared<VariableExpression>("x");return std::make_shared<NotExpression>(x);} else if (input == "x AND y") {auto x = std::make_shared<VariableExpression>("x");auto y = std::make_shared<VariableExpression>("y");return std::make_shared<AndExpression>(x, y);}// 默认返回falsereturn std::make_shared<ConstantExpression>(false);}
};// 客户端代码
int main() {Context context;context.setVariable("x", true);context.setVariable("y", false);// 解析并解释表达式auto expr1 = Parser::parse("x AND y");std::cout << "表达式 \"x AND y\" 的结果: " << (expr1->interpret(context) ? "true" : "false") << std::endl;auto expr2 = Parser::parse("NOT x");std::cout << "表达式 \"NOT x\" 的结果: " << (expr2->interpret(context) ? "true" : "false") << std::endl;return 0;
}
三、解释器模式的优势
  1. 可扩展性

    • 新增语法规则只需添加新的表达式类,符合开闭原则。
  2. 简化语法实现

    • 将复杂语法分解为多个简单表达式,易于维护。
  3. 灵活性

    • 相同语法树可通过不同上下文实现不同解释。
  4. 直观表示

    • 语法规则以类的形式表示,清晰直观。
四、实现变种
  1. 解析器生成器

    • 使用工具(如ANTLR)自动生成解释器,而非手动实现。
  2. 解释器与编译器结合

    • 先将语言编译为中间表示,再由解释器执行。
  3. 上下文优化

    • 使用共享上下文或线程局部上下文提高性能。
五、适用场景
  1. 领域特定语言(DSL)

    • 如正则表达式、SQL查询、配置文件解析。
  2. 简单语法解析

    • 如数学表达式计算、模板引擎。
  3. 重复出现的问题

    • 如日志过滤规则、权限表达式。
  4. 教育场景

    • 教学编译器原理或语言解释机制。
六、注意事项
  1. 性能限制

    • 解释执行效率通常低于编译执行,不适合复杂大规模语言。
  2. 复杂度控制

    • 过多语法规则会导致类数量激增,需合理组织。
  3. 安全性

    • 解释用户输入的表达式可能引入安全风险(如代码注入)。
  4. 替代方案

    • 复杂场景可考虑使用解析器生成工具(如Lex/Yacc)或直接编译为字节码。

解释器模式通过将语言语法规则映射为对象层次结构,提供了一种优雅的方式来解析和执行简单语言。在需要自定义DSL或处理特定语法的场景中,该模式能有效简化实现。

访问者模式(Visitor Pattern)详解

一、核心概念

访问者模式允许在不改变对象结构的前提下,定义作用于这些对象的新操作。它将算法与对象结构分离,通过双分派(Double Dispatch)实现对不同类型元素的差异化处理。

核心组件

  1. 抽象访问者(Visitor):定义对每个具体元素的访问操作接口。
  2. 具体访问者(Concrete Visitor):实现抽象访问者接口,处理特定操作。
  3. 抽象元素(Element):定义接受访问者的接口(accept方法)。
  4. 具体元素(Concrete Element):实现接受访问者的逻辑,通常调用访问者的对应方法。
  5. 对象结构(Object Structure):管理元素集合,提供遍历元素的方式。
二、代码示例:文档元素的格式化处理

场景:文档包含文本、图片等元素,需支持不同格式的导出(如HTML、Markdown)。

#include <iostream>
#include <string>
#include <memory>
#include <vector>// 前向声明
class TextElement;
class ImageElement;// 抽象访问者
class Visitor {
public:virtual void visitText(const TextElement& text) = 0;virtual void visitImage(const ImageElement& image) = 0;virtual ~Visitor() = default;
};// 抽象元素
class Element {
public:virtual void accept(Visitor& visitor) const = 0;virtual ~Element() = default;
};// 具体元素:文本
class TextElement : public Element {
private:std::string content;public:explicit TextElement(const std::string& content) : content(content) {}void accept(Visitor& visitor) const override {visitor.visitText(*this);}std::string getContent() const { return content; }
};// 具体元素:图片
class ImageElement : public Element {
private:std::string url;public:explicit ImageElement(const std::string& url) : url(url) {}void accept(Visitor& visitor) const override {visitor.visitImage(*this);}std::string getUrl() const { return url; }
};// 具体访问者:HTML导出器
class HtmlExportVisitor : public Visitor {
public:void visitText(const TextElement& text) override {std::cout << "<p>" << text.getContent() << "</p>" << std::endl;}void visitImage(const ImageElement& image) override {std::cout << "<img src=\"" << image.getUrl() << "\" alt=\"图片\" />" << std::endl;}
};// 具体访问者:Markdown导出器
class MarkdownExportVisitor : public Visitor {
public:void visitText(const TextElement& text) override {std::cout << text.getContent() << std::endl << std::endl;}void visitImage(const ImageElement& image) override {std::cout << "![" << image.getUrl() << "](" << image.getUrl() << ")" << std::endl;}
};// 对象结构:文档
class Document {
private:std::vector<std::shared_ptr<Element>> elements;public:void addElement(std::shared_ptr<Element> element) {elements.push_back(element);}void accept(Visitor& visitor) const {for (const auto& element : elements) {element->accept(visitor);}}
};// 客户端代码
int main() {Document doc;doc.addElement(std::make_shared<TextElement>("欢迎使用访问者模式"));doc.addElement(std::make_shared<ImageElement>("https://example.com/logo.png"));doc.addElement(std::make_shared<TextElement>("这是一个示例文档"));std::cout << "=== HTML 导出 ===" << std::endl;HtmlExportVisitor htmlVisitor;doc.accept(htmlVisitor);std::cout << "\n=== Markdown 导出 ===" << std::endl;MarkdownExportVisitor markdownVisitor;doc.accept(markdownVisitor);return 0;
}
三、访问者模式的优势
  1. 开闭原则

    • 新增操作只需添加新的访问者,无需修改元素类。
  2. 操作集中化

    • 相关操作集中在一个访问者中,便于维护。
  3. 双分派机制

    • 通过acceptvisit方法的双重调用,动态确定元素类型和操作。
  4. 分离关注点

    • 元素的业务逻辑与操作解耦,提高代码可维护性。
四、实现变种
  1. 对象结构的实现

    • 可以是列表、树、图等任何数据结构。
  2. 访问者重载

    • 支持不同参数或返回值的访问方法。
  3. 访问者链

    • 多个访问者按顺序处理元素。
  4. 懒加载访问者

    • 在需要时动态创建访问者。
五、适用场景
  1. 对象结构稳定但操作多变

    • 如编译器的AST节点处理、XML解析。
  2. 需要对不同类型元素进行差异化操作

    • 如文档格式化、图形渲染。
  3. 跨层次操作元素

    • 如统计文档中不同元素的数量。
  4. 避免大量条件判断

    • 将类型判断逻辑封装在访问者中。
六、注意事项
  1. 破坏封装性

    • 访问者可能需要访问元素的私有状态,需谨慎设计。
  2. 元素类型固定

    • 新增元素类型需修改所有访问者,违反开闭原则。
  3. 复杂度增加

    • 模式引入多个类,可能增加系统复杂度。
  4. 性能开销

    • 双重分派可能带来一定性能损耗。

访问者模式通过分离对象结构和操作,提供了一种灵活的方式来扩展系统功能。在需要对稳定对象结构定义多种操作的场景中,该模式尤为有效。

http://www.dtcms.com/a/266378.html

相关文章:

  • [学习记录]Unity毛发渲染[URP]-Fin基础版
  • Django Channels WebSocket实时通信实战:从聊天功能到消息推送
  • Linux入门篇学习——Linux 帮助手册
  • 八、测试与调试
  • 万勋科技「柔韧机器人玻璃幕墙清洗」全国巡展@上海!引领清洗无人机智能化升级
  • Rovo Dev CLI Windows 安装与使用指南
  • 暑期数据结构第一天
  • CLIP的tokenizer详解
  • 2-jdk8环境下安装Kafka
  • 标签体系设计与管理:从理论基础到智能化实践的综合指南
  • chrome安装AXURE插件后无效
  • uniapp 微信小程序水印
  • c++游戏_小恐龙(开源)
  • Spring Boot + MyBatis/MyBatis Plus:XML中循环处理List参数的终极指南
  • MySQL安装报错解决
  • 解锁阿里云Hologres:开启实时数据分析新时代
  • [论文阅读] 人工智能 + 软件工程 | 需求获取访谈中LLM生成跟进问题研究:来龙去脉与创新突破
  • ODS 系统是什么?企业为什么需要搭建 ODS?
  • .net对象映射框架
  • Response对象
  • Gartner《数据与分析治理的参考架构概述》学习心得
  • electron 打包太大 试试 tauri , tauri 安装打包demo
  • 短剧系统开发定制全流程解析:从需求分析到上线的专业指南
  • 屏幕分辨率修改工具 SwitchResX(Mac电脑)
  • 2025.7.4总结
  • Compose LazyVerticalStaggeredGrid卡顿
  • Excel 如何处理更复杂的嵌套逻辑判断?
  • 【嵌入式电机控制#9】编码器滤波算法
  • 敏捷开发在国际化团队管理中的落地
  • 如何选择合适的工业相机快门种类