享元模式通过共享技术复用相同或相似的细粒度对象,以减少内存占用和提高性能。该模式将对象状态分为内部状态(可共享的不变部分)和外部状态(需外部传入的可变部分),通过共享内部状态降低对象数量。
核心组件:
在享元对象内部并且不会随环境改变而改变的共享部分,可以称为是享元对象的内部状态,而随环境改变而改变的、不可以共享的状态就是外部状态了。事实上,享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够受大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。也就是说,享元模式Flyweight执行时所需的状态是有内部的也可能有外部的,内部状态存储于ConcreteFlyweight对象之中,而外部对象则应该考虑由客户端对象存储或计算,当调用Flyweight对象的操作时,将该状态传递给它。” ——《大话设计模式》
“大鸟,你通过这个例子来讲解享元模式虽然我是理解了,但在现实中什么时候才应该考虑使用享元模式呢?”
“就知道你会问这样的问题,如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。”
场景:文本编辑器中,每个字符的字体、颜色等属性可共享,位置和内容为外部状态。
#include
#include
#include
#include
// 外部状态:字符位置
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;
}
减少内存占用:
提高性能:
集中管理状态:
符合开闭原则:
单纯享元 vs 复合享元:
静态工厂 vs 动态工厂:
弱引用缓存:
std::weak_ptr
管理享元对象,允许不再使用的对象被垃圾回收。系统中存在大量相似对象:
对象状态可分为内部/外部状态:
对象创建成本高:
需要缓存对象:
状态划分复杂性:
线程安全:
性能权衡:
与其他模式结合:
享元模式通过共享技术优化大量细粒度对象的内存占用,是一种典型的“以时间换空间”的优化策略。在需要处理大量相似对象的场景中,该模式能显著提升系统性能。
解释器模式用于定义语言的文法表示,并创建解释器来解析该语言中的句子。它将语言中的每个语法规则映射为一个类,使语法规则的实现和使用分离,适合简单的领域特定语言(DSL)。
核心组件:
“解释器模式有什么好处呢?”
“用了解释器模式,就意味着可以很容易地改变和扩展文法,因为该模式使用类来表示文法规则,你可使用继承来改变或扩展该文法。也比较容易实现文法,因为定义抽象语法树中各个节点的类的实现大体类似,这些类都易于直接编写[DP]。”
场景:实现一个简单的布尔表达式解释器,支持变量(如x
、y
)、逻辑与(AND
)、逻辑非(NOT
)。
#include
#include
#include
#include
#include
// 上下文:存储变量值
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);
}
// 默认返回false
return 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;
}
可扩展性:
简化语法实现:
灵活性:
直观表示:
解析器生成器:
解释器与编译器结合:
上下文优化:
领域特定语言(DSL):
简单语法解析:
重复出现的问题:
教育场景:
性能限制:
复杂度控制:
安全性:
替代方案:
解释器模式通过将语言语法规则映射为对象层次结构,提供了一种优雅的方式来解析和执行简单语言。在需要自定义DSL或处理特定语法的场景中,该模式能有效简化实现。
访问者模式允许在不改变对象结构的前提下,定义作用于这些对象的新操作。它将算法与对象结构分离,通过双分派(Double Dispatch)实现对不同类型元素的差异化处理。
核心组件:
accept
方法)。场景:文档包含文本、图片等元素,需支持不同格式的导出(如HTML、Markdown)。
#include
#include
#include
#include
// 前向声明
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 << ""
<< text.getContent() << "" << std::endl;
}
void visitImage(const ImageElement& image) override {
std::cout << "
<< 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 << " << ")" << 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;
}
开闭原则:
操作集中化:
双分派机制:
accept
和visit
方法的双重调用,动态确定元素类型和操作。分离关注点:
对象结构的实现:
访问者重载:
访问者链:
懒加载访问者:
对象结构稳定但操作多变:
需要对不同类型元素进行差异化操作:
跨层次操作元素:
避免大量条件判断:
破坏封装性:
元素类型固定:
复杂度增加:
性能开销:
访问者模式通过分离对象结构和操作,提供了一种灵活的方式来扩展系统功能。在需要对稳定对象结构定义多种操作的场景中,该模式尤为有效。