一、享元模式的核心认知
享元模式(Flyweight Pattern)是一种结构型设计模式,其核心思想是通过共享技术复用大量细粒度的相似对象,从而显著减少内存占用并提升系统性能。该模式的关键在于将对象的状态拆分为两类:
-
内部状态(Intrinsic State):对象固有的、可共享的、不随使用场景变化的属性。例如家具的材质(实木、板材)、基础款式(餐椅、书桌),这些属性在大量家具中重复出现,适合集中管理和共享。
-
外部状态(Extrinsic State):对象依赖于具体场景、不可共享的、动态变化的属性。例如家具的颜色(白色、原木色)、尺寸(45cm×45cm、60cm×60cm),这些属性因客户需求不同而变化,需由客户端单独维护并在使用时传入。
在家具生产场景中,若为每一件家具都创建独立对象(包含材质、款式、颜色、尺寸等所有属性),当生产数千件仅颜色或尺寸不同的实木餐椅时,会导致大量重复的材质和款式数据占用内存。享元模式通过共享"实木+餐椅"这一核心内部状态,仅为不同的颜色和尺寸维护外部状态,可将对象数量从"数千个"降至"几种核心款式",极大优化资源占用。
二、享元模式的结构与家具生产场景映射
享元模式通常包含四个核心角色,结合家具生产场景的映射关系如下:
-
抽象享元类(Flyweight):定义家具的公共接口,声明接收外部状态并执行核心操作(如生产)的方法。对应"家具"抽象类,定义生产家具的接口,参数包含颜色、尺寸等外部状态。
-
具体享元类(Concrete Flyweight):实现抽象享元接口,存储可共享的内部状态(如材质、款式)。对应"实木餐椅""板材书桌"等具体家具类,其材质和款式在创建后固定不变。
-
享元工厂类(Flyweight Factory):负责创建和管理享元对象,维护一个"享元池"(通常用哈希表实现)。当客户端请求家具时,工厂先检查池中是否存在对应内部状态的享元对象,若存在则直接返回,不存在则创建新对象并放入池中。对应"家具工厂",管理不同材质和款式的核心家具对象。
-
客户端(Client):维护家具的外部状态,通过享元工厂获取享元对象,并将外部状态传入享元对象的方法中执行操作。对应"家具店订单处理系统",根据客户订单的颜色、尺寸需求,从工厂获取核心家具对象并完成生产。
三、C++代码实现:家具生产系统
以下代码以家具厂生产餐椅和书桌为例,实现享元模式。核心逻辑为:共享"材质+款式"的内部状态,动态传入"颜色+尺寸"的外部状态。
cpp
#include <iostream>
#include <map>
#include <string>
#include <memory> // 用于智能指针管理内存
// 1. 抽象享元类:家具
class Furniture {
public:
virtual ~Furniture() = default;
// 生产家具的接口,参数为外部状态(颜色、尺寸)
virtual void produce(const std::string& color, const std::string& size) const = 0;
// 获取内部状态(材质+款式),用于工厂管理
virtual std::string getKey() const = 0;
};
// 2. 具体享元类1:餐椅
class Chair : public Furniture {
private:
// 内部状态:材质和款式(固定,可共享)
std::string material_;
std::string style_;
public:
// 构造函数初始化内部状态
Chair(std::string material, std::string style)
: material_(std::move(material)), style_(std::move(style)) {}
// 实现生产方法:结合内部状态和外部状态
void produce(const std::string& color, const std::string& size) const override {
std::cout << "生产完成:" << material_ << "材质-" << style_ << "餐椅,"
<< "颜色:" << color << ",尺寸:" << size << std::endl;
}
// 生成内部状态的键值,用于工厂的享元池查找
std::string getKey() const override {
return material_ + "_" + style_;
}
};
// 3. 具体享元类2:书桌
class Desk : public Furniture {
private:
// 内部状态:材质和款式
std::string material_;
std::string style_;
public:
Desk(std::string material, std::string style)
: material_(std::move(material)), style_(std::move(style)) {}
void produce(const std::string& color, const std::string& size) const override {
std::cout << "生产完成:" << material_ << "材质-" << style_ << "书桌,"
<< "颜色:" << color << ",尺寸:" << size << std::endl;
}
std::string getKey() const override {
return material_ + "_" + style_;
}
};
// 4. 享元工厂类:家具工厂
class FurnitureFactory {
private:
// 享元池:用map存储,键为内部状态组合(材质_款式),值为家具智能指针
std::map<std::string, std::shared_ptr<Furniture>> furniture_pool_;
public:
// 获取家具对象:根据内部状态查找,不存在则创建
std::shared_ptr<Furniture> getFurniture(const std::string& type,
const std::string& material,
const std::string& style) {
// 生成内部状态的键
std::string key = material + "_" + style;
// 检查享元池是否存在该对象
if (furniture_pool_.find(key) == furniture_pool_.end()) {
// 不存在则创建对应类型的具体享元对象
if (type == "Chair") {
furniture_pool_[key] = std::make_shared<Chair>(material, style);
} else if (type == "Desk") {
furniture_pool_[key] = std::make_shared<Desk>(material, style);
} else {
std::cout << "不支持的家具类型:" << type << std::endl;
return nullptr;
}
std::cout << "创建新的享元对象:" << key << std::endl;
} else {
std::cout << "复用享元对象:" << key << std::endl;
}
return furniture_pool_[key];
}
};
// 5. 客户端:家具店订单处理
int main() {
// 创建家具工厂
FurnitureFactory factory;
// 订单1:白色45cm实木休闲餐椅
auto chair1 = factory.getFurniture("Chair", "实木", "休闲");
if (chair1) chair1->produce("白色", "45cm×45cm");
// 订单2:原木色45cm实木休闲餐椅(复用内部状态)
auto chair2 = factory.getFurniture("Chair", "实木", "休闲");
if (chair2) chair2->produce("原木色", "45cm×45cm");
// 订单3:黑色120cm板材简约书桌
auto desk1 = factory.getFurniture("Desk", "板材", "简约");
if (desk1) desk1->produce("黑色", "120cm×60cm");
// 订单4:灰色100cm板材简约书桌(复用内部状态)
auto desk2 = factory.getFurniture("Desk", "板材", "简约");
if (desk2) desk2->produce("灰色", "100cm×60cm");
// 订单5:红色50cm实木北欧餐椅(新内部状态)
auto chair3 = factory.getFurniture("Chair", "实木", "北欧");
if (chair3) chair3->produce("红色", "50cm×50cm");
return 0;
}
四、代码解析
-
抽象享元类(Furniture) :定义了
produce方法接收外部状态(颜色、尺寸),getKey方法用于生成内部状态的键值,为工厂管理提供依据。 -
具体享元类(Chair/Desk) :存储"材质+款式"的内部状态,在
produce方法中结合外部状态完成生产逻辑。内部状态在构造后固定,确保可共享性。 -
享元工厂(FurnitureFactory) :通过
std::map实现享元池,getFurniture方法根据内部状态键值查找对象,避免重复创建。使用智能指针(std::shared_ptr)管理内存,防止内存泄漏。 -
客户端(main):模拟家具店订单,仅需传递外部状态(颜色、尺寸)和内部状态(材质、款式),由工厂负责对象的复用或创建,客户端无需关注对象的具体管理逻辑。
五、享元模式的适用场景与注意事项
5.1 适用场景
-
系统中存在大量细粒度的相似对象,且对象数量导致内存占用过高(如家具生产、游戏中的大量相同模型角色、文本编辑器中的字符)。
-
对象的状态可清晰拆分为可共享的内部状态和不可共享的外部状态。
-
需要通过复用对象提升系统性能或降低创建对象的开销。
5.2 注意事项
-
状态划分清晰:必须严格区分内部状态和外部状态,内部状态需保证不可变(创建后不修改),否则会导致共享对象状态混乱。
-
工厂管理成本:享元工厂需维护享元池,若内部状态种类过多,会导致享元池过大,反而增加内存开销,需合理评估内部状态的种类数量。
-
线程安全 :多线程环境下,享元工厂的对象创建和获取需加锁(如
std::mutex),避免并发创建重复对象。
六、总结
享元模式通过"分离变与不变"的核心思想,在家具生产等场景中实现了对象的高效复用。其本质是用享元池管理共享状态,以少量共享对象替代大量相似对象,从而优化系统资源占用。在实际开发中,需结合场景合理划分状态,平衡复用收益与工厂管理成本,才能充分发挥享元模式的价值。