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),避免并发创建重复对象。

六、总结

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

相关推荐
自由生长20244 小时前
设计模式和设计原则-中高级架构思路-面向接口编程
设计模式
粉红色回忆7 小时前
用链表实现了简单版本的malloc/free函数
数据结构·c++
写代码的小球8 小时前
C++计算器(学生版)
c++·算法
k***92169 小时前
【C++】继承和多态扩展学习
java·c++·学习
序属秋秋秋9 小时前
《Linux系统编程之进程控制》【进程等待】
linux·c语言·c++·进程·系统编程·进程控制·进程等待
l木本I10 小时前
Reinforcement Learning for VLA(强化学习+VLA)
c++·人工智能·python·机器学习·机器人
strive programming10 小时前
Effective C++_异常(解剖挖掘)
c++
wregjru11 小时前
【读书笔记】Effective C++ 条款1~2 核心编程准则
java·开发语言·c++
青岛少儿编程-王老师12 小时前
CCF编程能力等级认证GESP—C++1级—20251227
java·c++·算法
微露清风12 小时前
系统性学习C++进阶-第十四讲-二叉搜索树
开发语言·c++·学习