C++设计模式之享元模式:以家具生产为例

一、享元模式的核心认知

享元模式(Flyweight Pattern)是一种结构型设计模式,其核心思想是通过共享技术复用大量细粒度的相似对象,从而显著减少内存占用并提升系统性能。该模式的关键在于将对象的状态拆分为两类:

  • 内部状态(Intrinsic State):对象固有的、可共享的、不随使用场景变化的属性。例如家具的材质(实木、板材)、基础款式(餐椅、书桌),这些属性在大量家具中重复出现,适合集中管理和共享。

  • 外部状态(Extrinsic State):对象依赖于具体场景、不可共享的、动态变化的属性。例如家具的颜色(白色、原木色)、尺寸(45cm×45cm、60cm×60cm),这些属性因客户需求不同而变化,需由客户端单独维护并在使用时传入。

在家具生产场景中,若为每一件家具都创建独立对象(包含材质、款式、颜色、尺寸等所有属性),当生产数千件仅颜色或尺寸不同的实木餐椅时,会导致大量重复的材质和款式数据占用内存。享元模式通过共享"实木+餐椅"这一核心内部状态,仅为不同的颜色和尺寸维护外部状态,可将对象数量从"数千个"降至"几种核心款式",极大优化资源占用。

二、享元模式的结构与家具生产场景映射

享元模式通常包含四个核心角色,结合家具生产场景的映射关系如下:

  1. 抽象享元类(Flyweight):定义家具的公共接口,声明接收外部状态并执行核心操作(如生产)的方法。对应"家具"抽象类,定义生产家具的接口,参数包含颜色、尺寸等外部状态。

  2. 具体享元类(Concrete Flyweight):实现抽象享元接口,存储可共享的内部状态(如材质、款式)。对应"实木餐椅""板材书桌"等具体家具类,其材质和款式在创建后固定不变。

  3. 享元工厂类(Flyweight Factory):负责创建和管理享元对象,维护一个"享元池"(通常用哈希表实现)。当客户端请求家具时,工厂先检查池中是否存在对应内部状态的享元对象,若存在则直接返回,不存在则创建新对象并放入池中。对应"家具工厂",管理不同材质和款式的核心家具对象。

  4. 客户端(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),避免并发创建重复对象。

六、总结

享元模式通过"分离变与不变"的核心思想,在家具生产等场景中实现了对象的高效复用。其本质是用享元池管理共享状态,以少量共享对象替代大量相似对象,从而优化系统资源占用。在实际开发中,需结合场景合理划分状态,平衡复用收益与工厂管理成本,才能充分发挥享元模式的价值。

相关推荐
ZouZou老师2 小时前
C++设计模式之适配器模式:以家具生产为例
java·设计模式·适配器模式
小小晓.2 小时前
Pinely Round 4 (Div. 1 + Div. 2)
c++·算法
SHOJYS2 小时前
学习离线处理 [CSP-J 2022 山东] 部署
数据结构·c++·学习·算法
steins_甲乙3 小时前
C++并发编程(3)——资源竞争下的安全栈
开发语言·c++·安全
煤球王子3 小时前
学而时习之:C++中的异常处理2
c++
仰泳的熊猫4 小时前
1084 Broken Keyboard
数据结构·c++·算法·pat考试
我不会插花弄玉4 小时前
C++的内存管理【由浅入深-C++】
c++
CSDN_RTKLIB4 小时前
代码指令与属性配置
开发语言·c++
上不如老下不如小4 小时前
2025年第七届全国高校计算机能力挑战赛 决赛 C++组 编程题汇总
开发语言·c++
雍凉明月夜4 小时前
c++ 精学笔记记录Ⅱ
开发语言·c++·笔记·vscode