结构型模式:⑥享元模式(Flyweight Pattern)
核心思想
复用共享对象,减少内存占用:当系统中存在大量相似对象时,将对象的「内部状态(可共享、不变)」与「外部状态(不可共享、可变)」分离,通过一个 “享元工厂” 缓存共享的内部状态对象,客户端仅需传入外部状态即可复用已有对象,无需重复创建,从而节省内存开销。
核心本质
分离状态 + 缓存复用,核心是 “共享” 相似对象的不变部分,可变部分由客户端动态传入,避免创建大量重复对象(例如围棋的黑白棋子,仅颜色是不变内部状态,位置是可变外部状态,无需创建 361 个棋子对象,只需 2 个共享对象)。
典型场景
围棋 / 象棋:大量棋子,仅颜色 / 类型是内部状态,位置是外部状态;
文字渲染:字符(如 ‘a’)是内部状态,渲染坐标是外部状态;
电商商品:商品基础信息(名称、价格)是内部状态,库存、销量是外部状态(或反之,根据共享需求调整)。
C语言编写
场景示例
围棋游戏:
内部状态:棋子颜色(黑 / 白,不变、共享);
外部状态:棋子位置(x,y 坐标,可变、不共享);
享元接口:定义棋子的显示方法(需接收外部状态);
享元工厂:缓存黑、白两个享元对象,客户端请求时直接返回,避免重复创建。
关键要点
1.状态分离:内部状态(棋子颜色)存储在享元结构体中(可共享、不变),外部状态(位置 x,y)通过函数参数传入(不可共享、可变),避免外部状态占用享元内存。
2.享元工厂核心:工厂缓存有限的享元对象(仅黑、白两个),客户端请求时优先复用已有对象,仅在首次请求时创建,实现 “一次创建、多次复用”。
3.接口统一:所有享元通过 ChessFlyweight 结构体的 display 函数指针提供统一接口,客户端无需区分具体享元类型,仅需传入外部状态。
4.内存优化:原本需要创建 N 个棋子对象(如围棋 361 个),现在仅需创建 2 个享元对象,大幅减少内存占用。
5.内存管理:享元对象由工厂统一创建和销毁,客户端不负责释放,避免内存泄漏或重复释放。
6.共享条件:内部状态必须是 “不变的”(如棋子颜色一旦创建不可修改),否则修改一个享元的内部状态会影响所有复用该对象的客户端。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 最大颜色描述长度
#define MAX_COLOR 16
// -------------------------- 1. 享元接口(Flyweight):棋子统一接口 --------------------------
typedef struct ChessFlyweight {
char color[MAX_COLOR]; // 内部状态:棋子颜色(可共享、不变)
// 显示棋子(需传入外部状态:坐标x,y)
void (*display)(struct ChessFlyweight* self, int x, int y);
} ChessFlyweight;
// -------------------------- 2. 具体享元(ConcreteFlyweight):黑棋/白棋 --------------------------
// 具体享元:黑棋
void black_chess_display(ChessFlyweight* self, int x, int y) {
printf("棋子类型:%s,位置:(%d, %d) [享元地址:%p]\n",
self->color, x, y, self);
}
// 具体享元:白棋
void white_chess_display(ChessFlyweight* self, int x, int y) {
printf("棋子类型:%s,位置:(%d, %d) [享元地址:%p]\n",
self->color, x, y, self);
}
// -------------------------- 3. 享元工厂(Flyweight Factory):缓存共享享元 --------------------------
typedef struct ChessFlyweightFactory {
ChessFlyweight* black_chess; // 缓存黑棋享元
ChessFlyweight* white_chess; // 缓存白棋享元
// 获取享元对象(根据颜色返回缓存的对象)
ChessFlyweight* (*get_chess)(struct ChessFlyweightFactory* self, const char* color);
// 销毁工厂(释放所有缓存的享元)
void (*destroy)(struct ChessFlyweightFactory* self);
} ChessFlyweightFactory;
// 工厂:获取享元(核心逻辑:复用缓存,不重复创建)
ChessFlyweight* factory_get_chess(ChessFlyweightFactory* self, const char* color) {
if (strcmp(color, "黑色") == 0) {
// 复用缓存的黑棋享元
if (self->black_chess == NULL) {
self->black_chess = (ChessFlyweight*)malloc(sizeof(ChessFlyweight));
strncpy(self->black_chess->color, "黑色", MAX_COLOR);
self->black_chess->display = black_chess_display;
printf("工厂:创建新的%s棋子享元\n", color);
} else {
printf("工厂:复用已有的%s棋子享元\n", color);
}
return self->black_chess;
} else if (strcmp(color, "白色") == 0) {
// 复用缓存的白棋享元
if (self->white_chess == NULL) {
self->white_chess = (ChessFlyweight*)malloc(sizeof(ChessFlyweight));
strncpy(self->white_chess->color, "白色", MAX_COLOR);
self->white_chess->display = white_chess_display;
printf("工厂:创建新的%s棋子享元\n", color);
} else {
printf("工厂:复用已有的%s棋子享元\n", color);
}
return self->white_chess;
} else {
printf("错误:不支持的棋子颜色:%s\n", color);
return NULL;
}
}
// 工厂:销毁(释放所有缓存的享元)
void factory_destroy(ChessFlyweightFactory* self) {
if (self != NULL) {
free(self->black_chess);
free(self->white_chess);
free(self);
printf("工厂:销毁所有享元\n");
}
}
// 创建享元工厂(初始化缓存)
ChessFlyweightFactory* create_chess_factory() {
ChessFlyweightFactory* factory = (ChessFlyweightFactory*)malloc(sizeof(ChessFlyweightFactory));
factory->black_chess = NULL;
factory->white_chess = NULL;
factory->get_chess = factory_get_chess;
factory->destroy = factory_destroy;
return factory;
}
// -------------------------- 测试代码(客户端) --------------------------
int main() {
// 1. 创建享元工厂
ChessFlyweightFactory* factory = create_chess_factory();
printf("=== 第一次落子 ===\n");
// 2. 客户端获取黑棋享元(第一次创建)
ChessFlyweight* black1 = factory->get_chess(factory, "黑色");
black1->display(black1, 3, 3); // 传入外部状态:位置(3,3)
// 3. 客户端获取白棋享元(第一次创建)
ChessFlyweight* white1 = factory->get_chess(factory, "白色");
white1->display(white1, 7, 7); // 传入外部状态:位置(7,7)
printf("\n=== 第二次落子 ===\n");
// 4. 客户端再次获取黑棋享元(复用已有对象)
ChessFlyweight* black2 = factory->get_chess(factory, "黑色");
black2->display(black2, 4, 4); // 传入新的外部状态:位置(4,4)
// 5. 客户端再次获取白棋享元(复用已有对象)
ChessFlyweight* white2 = factory->get_chess(factory, "白色");
white2->display(white2, 6, 6); // 传入新的外部状态:位置(6,6)
// 6. 验证享元复用(黑棋1和黑棋2地址相同,白棋同理)
printf("\n=== 享元复用验证 ===\n");
printf("黑棋1地址:%p,黑棋2地址:%p(%s)\n",
black1, black2, (black1 == black2) ? "复用成功" : "复用失败");
printf("白棋1地址:%p,白棋2地址:%p(%s)\n",
white1, white2, (white1 == white2) ? "复用成功" : "复用失败");
// 7. 销毁工厂
factory->destroy(factory);
return 0;
}
C++语言实现(类 + 继承 + 缓存工厂 + 智能指针)
场景示例
同 C 语言:围棋游戏(抽象享元 ChessFlyweight、具体享元 BlackChess/WhiteChess、享元工厂 ChessFactory),复用黑白棋子对象。通过 “抽象享元基类 + 具体享元子类” 定义接口,享元工厂用容器缓存共享对象,结合智能指针自动管理内存,代码更简洁、类型更安全。
关键要点
1.抽象基类定义接口:ChessFlyweight 用纯虚函数 Display/GetColor 定义统一接口,具体享元(BlackChess/WhiteChess)实现接口,确保多态调用。
2.工厂缓存逻辑:工厂用 std::map 存储享元(key 为内部状态 “颜色”),客户端请求时先查缓存,存在则直接返回,不存在则创建并缓存,实现高效复用。
3.智能指针管理内存:使用 std::shared_ptr 管理享元对象,工厂和客户端无需手动释放内存,避免内存泄漏;虚析构函数确保子类析构被正确调用。
4.状态分离清晰:内部状态(颜色)封装在具体享元类中(不可修改),外部状态(坐标)通过 Display 方法参数传入(客户端动态控制),符合 “分离可变与不可变” 的核心思想。
5.扩展灵活:新增棋子类型(如 “灰色棋子”)时,只需新增 GrayChess 类继承 ChessFlyweight,工厂无需修改核心逻辑(仅需在 GetChess 中添加颜色判断),符合开闭原则。
6.类型安全:C++ 的编译期类型检查和多态机制,避免 C 语言中函数指针绑定错误或类型转换风险,代码更可靠。
#include <iostream>
#include <string>
#include <map>
#include <memory> // 智能指针(自动管理内存)
// -------------------------- 1. 抽象享元(Flyweight):棋子基类 --------------------------
class ChessFlyweight {
public:
virtual ~ChessFlyweight() = default; // 虚析构:确保子类析构被调用
// 纯虚函数:显示棋子(接收外部状态:坐标x,y)
virtual void Display(int x, int y) const = 0;
// 获取内部状态:颜色
virtual std::string GetColor() const = 0;
};
// -------------------------- 2. 具体享元(ConcreteFlyweight):黑棋/白棋 --------------------------
// 具体享元:黑棋
class BlackChess : public ChessFlyweight {
public:
std::string GetColor() const override {
return "黑色";
}
void Display(int x, int y) const override {
std::cout << "棋子类型:" << GetColor() << ",位置:(" << x << ", " << y
<< ") [享元地址:" << this << "]" << std::endl;
}
};
// 具体享元:白棋
class WhiteChess : public ChessFlyweight {
public:
std::string GetColor() const override {
return "白色";
}
void Display(int x, int y) const override {
std::cout << "棋子类型:" << GetColor() << ",位置:(" << x << ", " << y
<< ") [享元地址:" << this << "]" << std::endl;
}
};
// -------------------------- 3. 享元工厂(Flyweight Factory):缓存共享享元 --------------------------
class ChessFactory {
public:
~ChessFactory() {
std::cout << "工厂:销毁所有享元" << std::endl;
}
// 获取享元对象(核心:复用缓存,不重复创建)
std::shared_ptr<ChessFlyweight> GetChess(const std::string& color) {
// 检查缓存中是否存在该颜色的享元
if (chess_map_.find(color) != chess_map_.end()) {
std::cout << "工厂:复用已有的" << color << "棋子享元" << std::endl;
return chess_map_[color];
}
// 缓存中不存在,创建新享元并加入缓存
std::shared_ptr<ChessFlyweight> chess = nullptr;
if (color == "黑色") {
chess = std::make_shared<BlackChess>();
} else if (color == "白色") {
chess = std::make_shared<WhiteChess>();
} else {
std::cout << "错误:不支持的棋子颜色:" << color << std::endl;
return nullptr;
}
chess_map_[color] = chess;
std::cout << "工厂:创建新的" << color << "棋子享元" << std::endl;
return chess;
}
private:
// 缓存享元的容器(key:颜色,value:享元智能指针)
std::map<std::string, std::shared_ptr<ChessFlyweight>> chess_map_;
};
// -------------------------- 测试代码(客户端) --------------------------
int main() {
// 1. 创建享元工厂
ChessFactory factory;
std::cout << "=== 第一次落子 ===" << std::endl;
// 2. 客户端获取黑棋享元(第一次创建)
auto black1 = factory.GetChess("黑色");
black1->Display(3, 3); // 传入外部状态:位置(3,3)
// 3. 客户端获取白棋享元(第一次创建)
auto white1 = factory.GetChess("白色");
white1->Display(7, 7); // 传入外部状态:位置(7,7)
std::cout << "\n=== 第二次落子 ===" << std::endl;
// 4. 客户端再次获取黑棋享元(复用已有对象)
auto black2 = factory.GetChess("黑色");
black2->Display(4, 4); // 传入新的外部状态:位置(4,4)
// 5. 客户端再次获取白棋享元(复用已有对象)
auto white2 = factory.GetChess("白色");
white2->Display(6, 6); // 传入新的外部状态:位置(6,6)
// 6. 验证享元复用(黑棋1和黑棋2指向同一个对象,白棋同理)
std::cout << "\n=== 享元复用验证 ===" << std::endl;
std::cout << "黑棋1地址:" << black1.get() << ",黑棋2地址:" << black2.get()
<< "(" << (black1 == black2 ? "复用成功" : "复用失败") << ")" << std::endl;
std::cout << "白棋1地址:" << white1.get() << ",白棋2地址:" << white2.get()
<< "(" << (white1 == white2 ? "复用成功" : "复用失败") << ")" << std::endl;
return 0; // 智能指针自动销毁享元,工厂销毁时清理容器
}
享元模式核心总结(C vs C++)

设计原则
1.单一职责原则:享元负责存储内部状态和处理核心逻辑,工厂负责缓存和创建,职责清晰;
2.开闭原则:新增享元类型时,仅需新增具体享元类,工厂无需大幅修改;
3.最小内存原则:通过共享内部状态,将对象数量从 “N 个” 减少到 “M 个”(M 为内部状态的种类数)。
1084

被折叠的 条评论
为什么被折叠?



