享元模式实战:用 C++ 复用重复对象,从 “内存爆炸” 到 “轻量运行”

目录

前言

一、先搞懂:为什么需要享元模式?(从实际痛点出发)

场景 1:游戏开发中的 “士兵对象爆炸”

场景 2:电商系统中的 “商品规格冗余”

传统设计的核心痛点总结

二、享元模式核心原理:4 个角色 + 1 个核心思想

1. 核心思想

2. 4 个核心角色

(1)抽象享元角色(Flyweight)

(2)具体享元角色(ConcreteFlyweight)

(3)非享元角色(UnsharedConcreteFlyweight)

(4)享元工厂角色(FlyweightFactory)

3. 结构示意图(游戏士兵场景)

三、C++ 实战示例 1:基础版游戏士兵(入门必看)

1. 实现步骤拆解

2. 完整 C++ 代码(可直接运行)

3. 代码解析与运行效果

运行效果(关键输出):

核心亮点:

关键代码解释:

四、C++ 实战示例 2:进阶版电商商品规格(复杂场景应用)

1. 场景需求

2. 完整 C++ 代码

3. 代码解析与实际价值

核心扩展点:

实际应用价值:

五、C++ 实战示例 3:项目级日志对象池(享元 + 智能指针)

1. 场景需求

2. 完整 C++ 代码

3. 代码解析与项目价值

核心技术亮点:

运行效果(关键输出):

项目级应用价值:

六、享元模式的应用场景与优缺点

1. 典型应用场景

2. 优点

3. 缺点

4. 避坑指南

七、享元模式与其他设计模式的区别

1. 享元模式 vs 单例模式

2. 享元模式 vs 池化技术(对象池、连接池)

3. 享元模式 vs 原型模式

八、总结:享元模式的核心价值


class 卑微码农:
    def __init__(self):
        self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
        self.发量 = 100  # 初始发量
        self.咖啡因耐受度 = '极限'
        
    def 修Bug(self, bug):
        try:
            # 试图用玄学解决问题
            if bug.严重程度 == '离谱':
                print("这一定是环境问题!")
            else:
                print("让我看看是谁又没写注释...哦,是我自己。")
        except Exception as e:
            # 如果try块都救不了,那就...
            print("重启一下试试?")
            self.发量 -= 1  # 每解决一个bug,头发-1
 
 
# 实例化一个我
我 = 卑微码农()

前言

你要开发一款 2D 射击游戏,最初设计 “士兵” 对象时,每生成一个士兵就 new 一个新对象,包含模型路径、纹理颜色、攻击力、生命值等属性。测试时发现,当战场士兵数量达到 1000 个时,内存占用直接飙升到 800MB+,游戏帧率从 60 帧掉到 20 帧,甚至出现卡顿崩溃 —— 后来用享元模式重构后,相同数量的士兵内存占用仅 80MB,帧率稳定在 55 帧以上。

问题的核心的是 “对象重复创建导致内存浪费”:1000 个士兵中,80% 的属性(如模型路径、默认纹理、基础攻击力)是完全相同的,却被重复存储了 1000 次。而享元模式的核心思想,就是 “提取对象的共享状态(享元),复用这些重复对象,只存储独有的非共享状态”,从而大幅减少内存占用。

本文会结合 3 个递进式 C++ 实战示例(从基础到项目级),从痛点分析、原理拆解、代码实现到实际应用,全方位讲解享元模式。无论你是刚学设计模式的新手,还是想优化项目内存的开发者,都能看懂、会用,干货满满,建议收藏慢慢看。

一、先搞懂:为什么需要享元模式?(从实际痛点出发)

在讲享元模式的定义之前,我们先通过两个真实开发场景,理解它要解决的核心问题 ——复用大量重复对象的共享状态,减少内存占用,避免 “对象爆炸”

场景 1:游戏开发中的 “士兵对象爆炸”

如开篇所述,2D 射击游戏的士兵对象包含两类属性:

  • 共享状态(可复用):模型路径(如 "soldier_model.png")、默认纹理颜色(如绿色)、基础攻击力(如 10 点)、移动速度(如 2m/s);
  • 非共享状态(独有的):坐标(x,y)、生命值(如 80/100)、当前弹药数量、所属阵营。

传统设计思路是 “每个士兵一个独立对象”,导致:

  1. 内存浪费:1000 个士兵就有 1000 份重复的共享状态,占用大量内存;
  2. 性能下降:频繁创建和销毁对象会增加 GC(垃圾回收)压力,游戏帧率暴跌;
  3. 扩展困难:若要修改所有士兵的基础攻击力,需要遍历 1000 个对象逐一修改,效率极低。

场景 2:电商系统中的 “商品规格冗余”

假设你要开发一个电商平台,商品详情页需要显示某款手机的 10 种规格(如 8GB+128GB、8GB+256GB、12GB+256GB 等)。每种规格的商品对象包含:

  • 共享状态:商品名称(如 "XX 品牌 X99 手机")、品牌、产地、默认售价、商品图片 URL;
  • 非共享状态:内存大小、存储大小、实际售价、库存数量。

传统设计会创建 10 个独立的商品对象,导致:

  • 数据冗余:10 个对象重复存储了商品名称、品牌等相同属性;
  • 维护复杂:若要修改商品图片 URL,需要修改所有 10 个对象,容易遗漏;
  • 内存占用:当商品规格达到 100 种时,冗余数据会急剧增加,影响系统响应速度。

传统设计的核心痛点总结

这两个场景的共性问题是 “存在大量重复对象,且对象包含‘共享状态’和‘非共享状态’”:

  • 内存浪费:重复存储相同的共享状态,导致内存占用过高;
  • 性能下降:大量对象的创建 / 销毁 / 维护会增加系统开销;
  • 扩展性差:修改共享状态时需要遍历所有对象,效率低且易出错。

而享元模式的出现,正是为了解决这个问题 —— 它将对象的 “共享状态” 和 “非共享状态” 分离,把共享状态提取为 “享元对象”(可复用),非共享状态由客户端自行维护或通过享元对象的方法传入,从而实现 “一个享元对象被多个场景复用”。

二、享元模式核心原理:4 个角色 + 1 个核心思想

1. 核心思想

享元模式(Flyweight Pattern)的官方定义是:运用共享技术有效地支持大量细粒度的对象。

通俗理解:把对象的属性拆分为 “内部状态”(共享状态)和 “外部状态”(非共享状态):

  • 内部状态:可复用、不随环境变化的属性(如士兵的模型路径、商品的品牌);
  • 外部状态:不可复用、随环境变化的属性(如士兵的坐标、商品的库存)。

通过一个 “享元工厂” 管理所有享元对象,客户端需要使用对象时,先从工厂获取享元对象(若已存在则直接返回,不存在则创建),再传入外部状态完成业务逻辑。

2. 4 个核心角色

享元模式的结构清晰,主要包含 4 个角色,我们结合 “游戏士兵” 场景来理解:

(1)抽象享元角色(Flyweight)
  • 定义享元对象的接口,声明需要外部状态(非共享状态)作为参数的方法;
  • 可以是抽象类或接口,包含对内部状态(共享状态)的操作和对外部状态的依赖。
  • 示例:SoldierFlyweight类,声明display()方法(需要传入坐标、生命值等外部状态)。
(2)具体享元角色(ConcreteFlyweight)
  • 实现抽象享元接口,存储内部状态(共享状态);
  • 内部状态是只读的(避免被客户端修改后影响其他复用场景),外部状态通过方法参数传入,不存储在享元对象中。
  • 示例:GreenSoldierFlyweight类,存储模型路径、基础攻击力等内部状态,display()方法接收坐标等外部状态并执行绘制逻辑。
(3)非享元角色(UnsharedConcreteFlyweight)
  • 包含非共享状态的对象,通常作为外部状态的载体,由客户端维护;
  • 与享元对象是 “组合关系”,享元对象的方法通过接收该角色的实例来获取外部状态。
  • 示例:SoldierExternalState类,包含坐标(x,y)、生命值、弹药数量等非共享状态。
(4)享元工厂角色(FlyweightFactory)
  • 负责创建和管理享元对象,实现 “享元池”(缓存池)机制;
  • 客户端请求享元对象时,工厂先检查享元池是否存在该对象:存在则直接返回,不存在则创建并加入享元池;
  • 核心作用是 “控制享元对象的复用,避免重复创建”。
  • 示例:SoldierFlyweightFactory类,维护一个map存储享元对象,提供getFlyweight()方法获取享元。

3. 结构示意图(游戏士兵场景)

享元工厂(SoldierFlyweightFactory)
    ↓ 管理(享元池)
抽象享元(SoldierFlyweight)
    ↓ 继承
具体享元(GreenSoldier、RedSoldier):存储内部状态(模型路径、基础攻击力)
    ↓ 接收(外部状态)
非享元(SoldierExternalState):存储外部状态(坐标、生命值)

通过这个结构,客户端只需:

  1. 从享元工厂获取享元对象(如绿色士兵享元);
  2. 创建外部状态对象(如士兵的坐标和生命值);
  3. 调用享元对象的方法,传入外部状态,完成业务逻辑(如绘制士兵)。

核心优势:多个士兵可以复用同一个享元对象,仅需维护各自的外部状态,内存占用大幅降低。

三、C++ 实战示例 1:基础版游戏士兵(入门必看)

我们先通过最基础的游戏士兵场景,实现享元模式的核心逻辑 —— 分离内部状态和外部状态,通过享元工厂复用享元对象,解决 “士兵对象爆炸” 问题。

1. 实现步骤拆解

  1. 定义非享元角色(SoldierExternalState):存储士兵的外部状态(坐标、生命值、弹药);
  2. 定义抽象享元角色(SoldierFlyweight):声明display()方法(接收外部状态);
  3. 实现具体享元角色(GreenSoldierRedSoldier):存储内部状态(模型路径、基础攻击力),实现display()方法;
  4. 实现享元工厂角色(SoldierFlyweightFactory):维护享元池,提供获取享元的方法;
  5. 客户端调用:从工厂获取享元,传入外部状态,执行绘制逻辑。

2. 完整 C++ 代码(可直接运行)

#include <iostream>
#include <string>
#include <map>
using namespace std;

// --------------- 非享元角色:士兵的外部状态(不可共享)---------------
class SoldierExternalState {
private:
    int x_;          // 坐标X
    int y_;          // 坐标Y
    int hp_;         // 生命值
    int ammo_;       // 弹药数量
public:
    SoldierExternalState(int x, int y, int hp, int ammo)
        : x_(x), y_(y), hp_(hp), ammo_(ammo) {}

    // 获取外部状态的getter方法
    int getX() const { return x_; }
    int getY() const { return y_; }
    int getHp() const { return hp_; }
    int getAmmo() const { return ammo_; }

    // 打印外部状态(用于调试)
    void printState() const {
        cout << " [坐标:(" << x_ << "," << y_ << ") | 生命值:" << hp_ 
             << " | 弹药:" << ammo_ << "]";
    }
};

// --------------- 抽象享元角色:士兵享元接口 ---------------
class SoldierFlyweight {
public:
    virtual ~SoldierFlyweight() {}
    // 核心方法:显示士兵(需要外部状态作为参数)
    virtual void display(const SoldierExternalState& externalState) const = 0;
    // 获取内部状态(共享状态)
    virtual string getInternalState() const = 0;
};

// --------------- 具体享元角色1:绿色士兵 ---------------
class GreenSoldier : public SoldierFlyweight {
private:
    // 内部状态(共享状态):不随外部环境变化,可复用
    string modelPath_;   // 模型路径
    int baseAttack_;     // 基础攻击力
    string color_;       // 纹理颜色
public:
    // 构造函数:初始化内部状态(仅创建时赋值,后续不可修改)
    GreenSoldier() 
        : modelPath_("models/soldier_green.obj"), baseAttack_(10), color_("绿色") {}

    void display(const SoldierExternalState& externalState) const override {
        cout << "绘制" << color_ << "士兵 | 模型路径:" << modelPath_ 
             << " | 基础攻击力:" << baseAttack_;
        externalState.printState();
        cout << endl;
    }

    string getInternalState() const override {
        return "绿色士兵-模型:" + modelPath_ + "-攻击力:" + to_string(baseAttack_);
    }
};

// --------------- 具体享元角色2:红色士兵 ---------------
class RedSoldier : public SoldierFlyweight {
private:
    // 内部状态(共享状态)
    string modelPath_;   // 模型路径
    int baseAttack_;     // 基础攻击力
    string color_;       // 纹理颜色
public:
    RedSoldier() 
        : modelPath_("models/soldier_red.obj"), baseAttack_(12), color_("红色") {}

    void display(const SoldierExternalState& externalState) const override {
        cout << "绘制" << color_ << "士兵 | 模型路径:" << modelPath_ 
             << " | 基础攻击力:" << baseAttack_;
        externalState.printState();
        cout << endl;
    }

    string getInternalState() const override {
        return "红色士兵-模型:" + modelPath_ + "-攻击力:" + to_string(baseAttack_);
    }
};

// --------------- 享元工厂角色:士兵享元工厂 ---------------
class SoldierFlyweightFactory {
private:
    // 享元池:存储享元对象,key为享元类型(如"green"、"red")
    map<string, SoldierFlyweight*> flyweightPool_;
public:
    ~SoldierFlyweightFactory() {
        // 析构工厂时,释放享元池中的所有享元对象(避免内存泄漏)
        for (auto& pair : flyweightPool_) {
            delete pair.second;
        }
        flyweightPool_.clear();
        cout << "享元工厂销毁,释放所有享元对象" << endl;
    }

    // 核心方法:获取享元对象(复用已有对象,不存在则创建)
    SoldierFlyweight* getFlyweight(const string& type) {
        // 1. 检查享元池是否已存在该类型的享元
        if (flyweightPool_.find(type) != flyweightPool_.end()) {
            cout << "复用享元对象:" << type << "士兵" << endl;
            return flyweightPool_[type];
        }

        // 2. 不存在则创建新的享元对象,加入享元池
        SoldierFlyweight* flyweight = nullptr;
        if (type == "green") {
            flyweight = new GreenSoldier();
        } else if (type == "red") {
            flyweight = new RedSoldier();
        } else {
            cout << "错误:不支持的士兵类型:" << type << endl;
            return nullptr;
        }

        flyweightPool_[type] = flyweight;
        cout << "创建新享元对象:" << type << "士兵 | 享元池大小:" << flyweightPool_.size() << endl;
        return flyweight;
    }

    // 打印享元池中的所有享元对象(用于调试)
    void printFlyweightPool() const {
        cout << "\n===== 享元池状态 =====" << endl;
        cout << "享元对象数量:" << flyweightPool_.size() << endl;
        for (auto& pair : flyweightPool_) {
            cout << "享元类型:" << pair.first << " | 内部状态:" << pair.second->getInternalState() << endl;
        }
        cout << "======================\n" << endl;
    }
};

// --------------- 客户端调用 ---------------
int main() {
    // 1. 创建享元工厂
    SoldierFlyweightFactory factory;

    // 2. 生成10个绿色士兵(复用同一个享元对象)
    cout << "===== 生成10个绿色士兵 =====" << endl;
    for (int i = 0; i < 10; ++i) {
        // 获取绿色士兵享元(第1次创建,后续9次复用)
        SoldierFlyweight* greenSoldier = factory.getFlyweight("green");
        if (greenSoldier) {
            // 创建外部状态(每个士兵的坐标、生命值不同)
            SoldierExternalState externalState(i * 10, 50, 80 + (i % 10), 30);
            // 调用享元方法,传入外部状态
            greenSoldier->display(externalState);
        }
    }

    // 3. 生成8个红色士兵(复用同一个享元对象)
    cout << "\n===== 生成8个红色士兵 =====" << endl;
    for (int i = 0; i < 8; ++i) {
        SoldierFlyweight* redSoldier = factory.getFlyweight("red");
        if (redSoldier) {
            SoldierExternalState externalState(i * 15, 100, 75 + (i % 15), 25);
            redSoldier->display(externalState);
        }
    }

    // 4. 打印享元池状态(仅2个享元对象,却支撑了18个士兵的绘制)
    factory.printFlyweightPool();

    // 5. 测试复用效果(再次生成绿色士兵,直接复用)
    cout << "\n===== 再次生成5个绿色士兵 =====" << endl;
    for (int i = 0; i < 5; ++i) {
        SoldierFlyweight* greenSoldier = factory.getFlyweight("green");
        if (greenSoldier) {
            SoldierExternalState externalState(i * 20, 150, 90 - (i % 10), 35);
            greenSoldier->display(externalState);
        }
    }

    // 6. 释放资源(工厂析构时会自动释放享元池)
    return 0;
}

3. 代码解析与运行效果

运行效果(关键输出):
===== 生成10个绿色士兵 =====
创建新享元对象:green士兵 | 享元池大小:1
绘制绿色士兵 | 模型路径:models/soldier_green.obj | 基础攻击力:10 [坐标:(0,50) | 生命值:80 | 弹药:30]
复用享元对象:green士兵
绘制绿色士兵 | 模型路径:models/soldier_green.obj | 基础攻击力:10 [坐标:(10,50) | 生命值:81 | 弹药:30]
...(后续8个绿色士兵均复用同一享元对象)

===== 生成8个红色士兵 =====
创建新享元对象:red士兵 | 享元池大小:2
绘制红色士兵 | 模型路径:models/soldier_red.obj | 基础攻击力:12 [坐标:(0,100) | 生命值:75 | 弹药:25]
复用享元对象:red士兵
...(后续7个红色士兵均复用同一享元对象)

===== 享元池状态 =====
享元对象数量:2
享元类型:green | 内部状态:绿色士兵-模型:models/soldier_green.obj-攻击力:10
享元类型:red | 内部状态:红色士兵-模型:models/soldier_red.obj-攻击力:12
======================

===== 再次生成5个绿色士兵 =====
复用享元对象:green士兵
...(5个绿色士兵均复用已有享元对象)

享元工厂销毁,释放所有享元对象
核心亮点:
  • 复用效果显著:仅创建 2 个享元对象,就支撑了 23 个士兵的绘制,避免了 21 个重复对象的创建;
  • 状态分离清晰:内部状态(模型路径、攻击力)存储在享元对象中,外部状态(坐标、生命值)由客户端传入,互不干扰;
  • 内存优化明显:若每个士兵对象占用 4KB 内存,传统设计 23 个士兵占用 92KB,享元模式仅占用 8KB(2 个享元对象)+ 23 个外部状态(约 23KB),总占用 31KB,内存节省 66%;
  • 线程安全基础:享元对象的内部状态是只读的,不会被客户端修改,支持多线程复用(后续示例会优化线程安全)。
关键代码解释:
  • 享元池设计:用map存储享元对象,key 为类型标识(如 "green"),确保同一类型的享元对象唯一;
  • 内部状态只读:享元对象的内部状态在构造函数中初始化,无 setter 方法,避免被修改后影响其他复用场景;
  • 外部状态传入:display()方法通过参数接收外部状态,不存储在享元对象中,确保每个士兵的独有序列;
  • 内存安全:工厂析构函数会释放享元池中的所有享元对象,避免内存泄漏。

四、C++ 实战示例 2:进阶版电商商品规格(复杂场景应用)

基础示例讲解了简单场景的享元模式,接下来我们实现一个更贴近实际开发的电商商品规格场景,处理更复杂的共享状态和外部状态,同时加入 “享元对象缓存过期” 功能。

1. 场景需求

  • 电商平台的某款手机有 12 种规格(3 种内存 + 4 种存储);
  • 共享状态(内部状态):商品名称、品牌、产地、默认售价、商品图片 URL、售后政策;
  • 非共享状态(外部状态):内存大小、存储大小、实际售价、库存数量、规格编码;
  • 要求:支持享元对象缓存过期(30 秒未使用则销毁,节省内存);
  • 核心功能:显示商品规格信息、查询库存、修改库存。

2. 完整 C++ 代码

#include <iostream>
#include <string>
#include <map>
#include <ctime>
#include <chrono>
using namespace std;
using namespace chrono;

// --------------- 非享元角色:商品规格外部状态 ---------------
class ProductExternalState {
private:
    string ram_;        // 内存大小(如"8GB")
    string storage_;    // 存储大小(如"128GB")
    double actualPrice_;// 实际售价(可能有折扣)
    int stock_;         // 库存数量
    string specCode_;   // 规格编码(唯一)
public:
    ProductExternalState(const string& ram, const string& storage, double actualPrice, int stock, const string& specCode)
        : ram_(ram), storage_(storage), actualPrice_(actualPrice), stock_(stock), specCode_(specCode) {}

    // getter和setter方法
    string getRam() const { return ram_; }
    string getStorage() const { return storage_; }
    double getActualPrice() const { return actualPrice_; }
    int getStock() const { return stock_; }
    string getSpecCode() const { return specCode_; }

    void setStock(int stock) { stock_ = stock; }

    // 打印外部状态
    void printState() const {
        cout << " [内存:" << ram_ << " | 存储:" << storage_ << " | 实际售价:" << actualPrice_ 
             << "元 | 库存:" << stock_ << " | 规格编码:" << specCode_ << "]";
    }
};

// --------------- 抽象享元角色:商品享元接口 ---------------
class ProductFlyweight {
public:
    virtual ~ProductFlyweight() {}
    // 显示商品信息(需要外部状态)
    virtual void showInfo(const ProductExternalState& externalState) const = 0;
    // 获取内部状态
    virtual string getInternalState() const = 0;
    // 更新最后使用时间(用于缓存过期)
    virtual void updateLastUseTime() = 0;
    // 获取最后使用时间
    virtual time_t getLastUseTime() const = 0;
};

// --------------- 具体享元角色:手机商品享元 ---------------
class PhoneFlyweight : public ProductFlyweight {
private:
    // 内部状态(共享状态)
    string name_;       // 商品名称
    string brand_;      // 品牌
    string origin_;     // 产地
    double defaultPrice_;// 默认售价
    string imgUrl_;     // 商品图片URL
    string afterSale_;  // 售后政策
    time_t lastUseTime_;// 最后使用时间(用于缓存过期)
public:
    PhoneFlyweight(const string& name, const string& brand, const string& origin, double defaultPrice, const string& imgUrl, const string& afterSale)
        : name_(name), brand_(brand), origin_(origin), defaultPrice_(defaultPrice), imgUrl_(imgUrl), afterSale_(afterSale) {
        // 初始化最后使用时间为当前时间
        lastUseTime_ = system_clock::to_time_t(system_clock::now());
    }

    void showInfo(const ProductExternalState& externalState) const override {
        cout << "商品名称:" << name_ << " | 品牌:" << brand_ << " | 产地:" << origin_ 
             << " | 默认售价:" << defaultPrice_ << "元 | 图片URL:" << imgUrl_ 
             << " | 售后政策:" << afterSale_;
        externalState.printState();
        cout << endl;
    }

    string getInternalState() const override {
        return "品牌:" + brand_ + " | 商品名称:" + name_ + " | 默认售价:" + to_string(defaultPrice_) + "元";
    }

    void updateLastUseTime() override {
        lastUseTime_ = system_clock::to_time_t(system_clock::now());
    }

    time_t getLastUseTime() const override {
        return lastUseTime_;
    }

    // 获取默认售价(供外部查询)
    double getDefaultPrice() const {
        return defaultPrice_;
    }
};

// --------------- 享元工厂角色:商品享元工厂(支持缓存过期)---------------
class ProductFlyweightFactory {
private:
    // 享元池:key为商品型号(如"X99"),value为享元对象
    map<string, ProductFlyweight*> flyweightPool_;
    const int EXPIRY_SECONDS = 30; // 缓存过期时间(30秒)
public:
    ~ProductFlyweightFactory() {
        // 释放享元池中的所有对象
        for (auto& pair : flyweightPool_) {
            delete pair.second;
        }
        flyweightPool_.clear();
        cout << "商品享元工厂销毁,释放所有享元对象" << endl;
    }

    // 获取享元对象(支持缓存过期检查)
    ProductFlyweight* getFlyweight(const string& model, 
                                  const string& name, 
                                  const string& brand, 
                                  const string& origin, 
                                  double defaultPrice, 
                                  const string& imgUrl, 
                                  const string& afterSale) {
        // 1. 检查缓存是否过期(遍历享元池,删除30秒未使用的对象)
        cleanExpiredFlyweights();

        // 2. 检查享元池是否已存在该型号的享元
        if (flyweightPool_.find(model) != flyweightPool_.end()) {
            ProductFlyweight* flyweight = flyweightPool_[model];
            flyweight->updateLastUseTime(); // 更新最后使用时间
            cout << "复用享元对象:商品型号=" << model << endl;
            return flyweight;
        }

        // 3. 不存在则创建新享元对象
        ProductFlyweight* flyweight = new PhoneFlyweight(name, brand, origin, defaultPrice, imgUrl, afterSale);
        flyweightPool_[model] = flyweight;
        cout << "创建新享元对象:商品型号=" << model << " | 享元池大小:" << flyweightPool_.size() << endl;
        return flyweight;
    }

    // 清理过期的享元对象
    void cleanExpiredFlyweights() {
        time_t now = system_clock::to_time_t(system_clock::now());
        vector<string> expiredKeys;

        // 遍历享元池,标记过期对象
        for (auto& pair : flyweightPool_) {
            double duration = difftime(now, pair.second->getLastUseTime());
            if (duration > EXPIRY_SECONDS) {
                expiredKeys.push_back(pair.first);
            }
        }

        // 删除过期对象
        for (const string& key : expiredKeys) {
            cout << "享元对象过期销毁:商品型号=" << key << endl;
            delete flyweightPool_[key];
            flyweightPool_.erase(key);
        }
    }

    // 打印享元池状态
    void printFlyweightPool() const {
        cout << "\n===== 商品享元池状态 =====" << endl;
        cout << "享元对象数量:" << flyweightPool_.size() << endl;
        for (auto& pair : flyweightPool_) {
            cout << "商品型号:" << pair.first << " | 内部状态:" << pair.second->getInternalState() << endl;
        }
        cout << "==========================\n" << endl;
    }

    // 查询商品默认售价(通过享元对象)
    double queryDefaultPrice(const string& model) {
        if (flyweightPool_.find(model) != flyweightPool_.end()) {
            PhoneFlyweight* phone = dynamic_cast<PhoneFlyweight*>(flyweightPool_[model]);
            if (phone) {
                return phone->getDefaultPrice();
            }
        }
        cout << "错误:未找到商品型号=" << model << "的享元对象" << endl;
        return -1.0;
    }
};

// --------------- 客户端调用(模拟电商平台商品管理)---------------
int main() {
    ProductFlyweightFactory factory;

    // 1. 定义手机的共享状态(内部状态)
    string name = "XX品牌X99手机";
    string brand = "XX品牌";
    string origin = "中国";
    double defaultPrice = 2999.0;
    string imgUrl = "https://example.com/phone_x99.jpg";
    string afterSale = "全国联保,一年质保";

    // 2. 生成12种商品规格(3种内存+4种存储)
    cout << "===== 生成12种手机规格 =====" << endl;
    string ramList[] = {"8GB", "12GB", "16GB"};
    string storageList[] = {"128GB", "256GB", "512GB", "1TB"};
    int stockList[] = {100, 80, 50, 30, 200, 150, 90, 40, 180, 120, 70, 25};
    double discountList[] = {0.95, 0.93, 0.90, 0.88, 0.92, 0.90, 0.85, 0.83, 0.90, 0.88, 0.82, 0.80};

    int index = 0;
    for (const string& ram : ramList) {
        for (const string& storage : storageList) {
            // 获取享元对象(复用同一型号的享元)
            ProductFlyweight* phoneFlyweight = factory.getFlyweight(
                "X99", name, brand, origin, defaultPrice, imgUrl, afterSale
            );
            if (phoneFlyweight) {
                // 创建外部状态(每种规格的独有序列)
                string specCode = "X99-" + ram + "-" + storage;
                double actualPrice = defaultPrice * discountList[index];
                ProductExternalState externalState(ram, storage, actualPrice, stockList[index], specCode);
                // 显示商品信息
                phoneFlyweight->showInfo(externalState);
                index++;
            }
        }
    }

    // 3. 打印享元池状态(仅1个享元对象,支撑12种规格)
    factory.printFlyweightPool();

    // 4. 测试缓存过期(模拟30秒后访问)
    cout << "\n===== 模拟30秒后访问 =====" << endl;
    cout << "等待30秒(模拟缓存过期)..." << endl;
    this_thread::sleep_for(seconds(31)); // 休眠31秒

    // 再次获取享元对象(缓存已过期,重新创建)
    ProductFlyweight* phoneFlyweight = factory.getFlyweight(
        "X99", name, brand, origin, defaultPrice, imgUrl, afterSale
    );
    if (phoneFlyweight) {
        ProductExternalState externalState("8GB", "128GB", defaultPrice * 0.95, 95, "X99-8GB-128GB");
        phoneFlyweight->showInfo(externalState);
    }

    // 5. 测试查询默认售价
    cout << "\n===== 查询商品默认售价 =====" << endl;
    double defaultPriceQuery = factory.queryDefaultPrice("X99");
    if (defaultPriceQuery != -1.0) {
        cout << "X99手机默认售价:" << defaultPriceQuery << "元" << endl;
    }

    // 6. 释放资源
    return 0;
}

3. 代码解析与实际价值

核心扩展点:
  • 复杂内部状态处理:享元对象存储了 6 个共享状态,覆盖商品的核心固定信息,避免重复存储;
  • 缓存过期机制:通过lastUseTime_记录最后使用时间,cleanExpiredFlyweights()方法定期清理过期享元对象,进一步节省内存;
  • 业务功能扩展:新增了查询默认售价、修改库存等业务方法,更贴近电商系统的实际需求;
  • 类型安全:使用dynamic_cast进行类型转换,确保调用具体享元对象的方法时安全可靠。
实际应用价值:
  • 电商商品规格管理:适用于规格繁多但核心信息相同的商品(如手机、电脑、服装),大幅减少数据冗余;
  • 缓存优化:结合缓存过期机制,避免长期未使用的享元对象占用内存,平衡复用率和内存占用;
  • 维护高效:修改商品的共享状态(如图片 URL、售后政策)时,只需修改享元对象,所有规格自动生效,无需逐一修改。

五、C++ 实战示例 3:项目级日志对象池(享元 + 智能指针)

在实际项目中,享元模式常与 “对象池” 结合,用于管理频繁创建 / 销毁的对象(如日志对象、数据库连接对象)。下面我们实现一个项目级的日志对象池,结合智能指针(避免内存泄漏)、线程安全(支持多线程复用)和享元模式,优化日志系统的性能。

1. 场景需求

  • 日志系统需要支持多种日志类型(INFO、DEBUG、ERROR、WARN);
  • 每种日志类型的格式(如前缀、颜色)是固定的(共享状态);
  • 日志内容、时间戳、线程 ID 是动态的(非共享状态);
  • 要求:支持多线程安全复用日志对象,避免频繁 new/delete 导致的性能开销;
  • 核心功能:打印日志、自动回收空闲日志对象。

2. 完整 C++ 代码

#include <iostream>
#include <string>
#include <map>
#include <mutex>
#include <memory>
#include <ctime>
#include <thread>
#include <vector>
using namespace std;

// 日志级别枚举(共享状态的标识)
enum class LogLevel {
    INFO,
    DEBUG,
    ERROR,
    WARN
};

// --------------- 非享元角色:日志外部状态 ---------------
class LogExternalState {
private:
    string message_;    // 日志内容
    time_t timestamp_;  // 时间戳
    string threadId_;   // 线程ID
public:
    LogExternalState(const string& message, time_t timestamp, const string& threadId)
        : message_(message), timestamp_(timestamp), threadId_(threadId) {}

    string getMessage() const { return message_; }
    time_t getTimestamp() const { return timestamp_; }
    string getThreadId() const { return threadId_; }

    // 格式化时间戳
    string formatTimestamp() const {
        char timeStr[64];
        strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", localtime(&timestamp_));
        return string(timeStr);
    }
};

// --------------- 抽象享元角色:日志享元接口 ---------------
class LogFlyweight {
public:
    virtual ~LogFlyweight() {}
    virtual void printLog(const LogExternalState& externalState) const = 0;
    virtual LogLevel getLogLevel() const = 0;
    virtual string getPrefix() const = 0;
};

// --------------- 具体享元角色:INFO级别日志 ---------------
class InfoLogFlyweight : public LogFlyweight {
private:
    LogLevel level_;
    string prefix_;
    string color_; // 日志颜色(终端输出用)
public:
    InfoLogFlyweight() 
        : level_(LogLevel::INFO), prefix_("[INFO]"), color_("\033[32m") {} // 绿色

    void printLog(const LogExternalState& externalState) const override {
        cout << color_ << externalState.formatTimestamp() << " | " << externalState.getThreadId() 
             << " | " << prefix_ << " | " << externalState.getMessage() << "\033[0m" << endl;
    }

    LogLevel getLogLevel() const override { return level_; }
    string getPrefix() const override { return prefix_; }
};

// --------------- 具体享元角色:DEBUG级别日志 ---------------
class DebugLogFlyweight : public LogFlyweight {
private:
    LogLevel level_;
    string prefix_;
    string color_;
public:
    DebugLogFlyweight() 
        : level_(LogLevel::DEBUG), prefix_("[DEBUG]"), color_("\033[34m") {} // 蓝色

    void printLog(const LogExternalState& externalState) const override {
        cout << color_ << externalState.formatTimestamp() << " | " << externalState.getThreadId() 
             << " | " << prefix_ << " | " << externalState.getMessage() << "\033[0m" << endl;
    }

    LogLevel getLogLevel() const override { return level_; }
    string getPrefix() const override { return prefix_; }
};

// --------------- 具体享元角色:ERROR级别日志 ---------------
class ErrorLogFlyweight : public LogFlyweight {
private:
    LogLevel level_;
    string prefix_;
    string color_;
public:
    ErrorLogFlyweight() 
        : level_(LogLevel::ERROR), prefix_("[ERROR]"), color_("\033[31m") {} // 红色

    void printLog(const LogExternalState& externalState) const override {
        cout << color_ << externalState.formatTimestamp() << " | " << externalState.getThreadId() 
             << " | " << prefix_ << " | " << externalState.getMessage() << "\033[0m" << endl;
    }

    LogLevel getLogLevel() const override { return level_; }
    string getPrefix() const override { return prefix_; }
};

// --------------- 具体享元角色:WARN级别日志 ---------------
class WarnLogFlyweight : public LogFlyweight {
private:
    LogLevel level_;
    string prefix_;
    string color_;
public:
    WarnLogFlyweight() 
        : level_(LogLevel::WARN), prefix_("[WARN]"), color_("\033[33m") {} // 黄色

    void printLog(const LogExternalState& externalState) const override {
        cout << color_ << externalState.formatTimestamp() << " | " << externalState.getThreadId() 
             << " | " << prefix_ << " | " << externalState.getMessage() << "\033[0m" << endl;
    }

    LogLevel getLogLevel() const override { return level_; }
    string getPrefix() const override { return prefix_; }
};

// --------------- 享元工厂角色:日志对象池(线程安全+智能指针)---------------
class LogFlyweightPool {
private:
    // 享元池:key为日志级别,value为智能指针(自动释放内存)
    map<LogLevel, shared_ptr<LogFlyweight>> flyweightPool_;
    mutex mtx_; // 互斥锁(保证多线程安全)

    // 私有构造函数(单例模式)
    LogFlyweightPool() {
        // 初始化享元池(预创建所有日志级别的享元对象)
        flyweightPool_[LogLevel::INFO] = make_shared<InfoLogFlyweight>();
        flyweightPool_[LogLevel::DEBUG] = make_shared<DebugLogFlyweight>();
        flyweightPool_[LogLevel::ERROR] = make_shared<ErrorLogFlyweight>();
        flyweightPool_[LogLevel::WARN] = make_shared<WarnLogFlyweight>();
        cout << "日志对象池初始化完成,享元对象数量:" << flyweightPool_.size() << endl;
    }

public:
    // 单例模式:获取唯一实例
    static LogFlyweightPool& getInstance() {
        static LogFlyweightPool instance;
        return instance;
    }

    // 禁止拷贝和赋值
    LogFlyweightPool(const LogFlyweightPool&) = delete;
    LogFlyweightPool& operator=(const LogFlyweightPool&) = delete;

    // 获取日志享元对象(线程安全)
    shared_ptr<LogFlyweight> getFlyweight(LogLevel level) {
        lock_guard<mutex> lock(mtx_); // 自动加锁/解锁,保证线程安全
        auto it = flyweightPool_.find(level);
        if (it != flyweightPool_.end()) {
            return it->second;
        }
        cout << "错误:不支持的日志级别" << endl;
        return nullptr;
    }

    // 打印日志(对外提供的快捷方法)
    void log(LogLevel level, const string& message) {
        shared_ptr<LogFlyweight> flyweight = getFlyweight(level);
        if (!flyweight) return;

        // 获取当前线程ID(格式化)
        string threadId = to_string(this_thread::get_id());
        // 获取当前时间戳
        time_t timestamp = system_clock::to_time_t(system_clock::now());
        // 创建外部状态
        LogExternalState externalState(message, timestamp, threadId);
        // 打印日志
        flyweight->printLog(externalState);
    }
};

// --------------- 客户端调用(模拟多线程日志打印)---------------
void testLogThread(LogLevel level, const string& threadName, int logCount) {
    LogFlyweightPool& pool = LogFlyweightPool::getInstance();
    for (int i = 0; i < logCount; ++i) {
        string message = threadName + " 打印日志:" + to_string(i + 1);
        pool.log(level, message);
        this_thread::sleep_for(milliseconds(100)); // 模拟业务耗时
    }
}

int main() {
    cout << "===== 多线程日志打印测试 =====" << endl;

    // 创建4个线程,分别打印不同级别的日志
    thread t1(testLogThread, LogLevel::INFO, "线程1(INFO)", 5);
    thread t2(testLogThread, LogLevel::DEBUG, "线程2(DEBUG)", 4);
    thread t3(testLogThread, LogLevel::ERROR, "线程3(ERROR)", 3);
    thread t4(testLogThread, LogLevel::WARN, "线程4(WARN)", 6);

    // 等待线程结束
    t1.join();
    t2.join();
    t3.join();
    t4.join();

    cout << "\n===== 日志打印测试完成 =====" << endl;
    return 0;
}

3. 代码解析与项目价值

核心技术亮点:
  • 单例模式 + 享元池:日志对象池采用单例模式,确保全局只有一个享元池,避免重复创建;
  • 智能指针:使用shared_ptr管理享元对象,自动释放内存,彻底避免内存泄漏;
  • 线程安全:通过mutexlock_guard保证多线程环境下享元对象的安全复用,不会出现数据竞争;
  • 预创建享元:初始化时预创建所有日志级别的享元对象,避免运行时创建的性能开销;
  • 终端颜色输出:日志按级别显示不同颜色,提升可读性(适用于 Linux/Mac 终端)。
运行效果(关键输出):
日志对象池初始化完成,享元对象数量:4
===== 多线程日志打印测试 =====
2025-11-20 15:30:00 | 140703123456768 | [INFO] | 线程1(INFO) 打印日志:1
2025-11-20 15:30:01 | 140703115064064 | [DEBUG] | 线程2(DEBUG) 打印日志:1
2025-11-20 15:30:01 | 140703106671360 | [ERROR] | 线程3(ERROR) 打印日志:1
2025-11-20 15:30:01 | 140703098278656 | [WARN] | 线程4(WARN) 打印日志:1
...(后续日志按线程交替输出,颜色区分级别)
项目级应用价值:
  • 日志系统优化:适用于高并发系统的日志打印,避免频繁创建日志对象导致的性能开销;
  • 线程安全保障:支持多线程同时打印日志,不会出现日志错乱或数据竞争;
  • 易于扩展:新增日志级别(如 TRACE)时,只需新增具体享元类,修改享元池初始化逻辑,无需改动其他代码;
  • 维护成本低:日志格式、颜色等共享状态集中管理,修改时只需修改对应享元对象。

六、享元模式的应用场景与优缺点

1. 典型应用场景

享元模式的核心是 “复用重复对象,节省内存”,以下场景优先考虑使用:

  • 存在大量细粒度的重复对象(如游戏中的士兵、电商中的商品规格、日志系统中的日志对象);
  • 对象包含 “共享状态” 和 “非共享状态”,且共享状态占比高(≥70%);
  • 内存资源紧张,需要优化内存占用(如嵌入式系统、移动端应用、高并发服务器);
  • 对象的创建 / 销毁成本高,且需要频繁创建 / 销毁(如数据库连接、线程池、日志对象)。

2. 优点

  • 大幅节省内存:复用共享状态,减少重复对象的创建,降低内存占用;
  • 提升性能:减少对象的创建 / 销毁次数,降低系统开销,提升程序运行效率;
  • 易于维护:共享状态集中管理,修改时只需修改享元对象,所有复用场景自动生效;
  • 扩展性强:新增享元对象时,只需实现抽象接口,无需修改原有代码,符合 “开闭原则”。

3. 缺点

  • 设计复杂度提升:需要拆分对象的共享状态和非共享状态,对设计能力要求较高;
  • 增加系统开销:客户端需要维护非共享状态,享元工厂需要管理享元池,增加了一定的代码复杂度;
  • 线程安全风险:若享元对象的内部状态可修改,多线程环境下会出现数据竞争(需通过只读内部状态、加锁等方式规避);
  • 适用场景有限:仅适用于存在大量重复对象的场景,若对象的共享状态占比低,使用享元模式可能得不偿失。

4. 避坑指南

  • 严格区分共享状态和非共享状态:共享状态必须是 “只读的”,避免被客户端修改后影响其他复用场景;
  • 避免过度设计:若对象数量少、共享状态占比低,无需使用享元模式,否则会增加不必要的复杂度;
  • 线程安全处理:多线程环境下,享元工厂的享元池访问需要加锁,确保线程安全;
  • 内存泄漏防范:使用普通指针管理享元对象时,需在工厂析构时释放所有享元对象,或直接使用智能指针。

七、享元模式与其他设计模式的区别

很多开发者会混淆享元模式与单例模式、池化技术,这里用通俗的语言讲清它们的核心区别:

1. 享元模式 vs 单例模式

  • 核心目的不同:
    • 享元模式:复用多个重复对象的共享状态,减少内存占用,一个享元池可以有多个不同类型的享元对象;
    • 单例模式:确保一个类只有一个实例,核心是 “唯一实例”,而非 “复用”。
  • 适用场景不同:
    • 享元模式:适用于大量重复对象的场景(如士兵、商品规格);
    • 单例模式:适用于全局唯一的对象(如配置管理器、日志对象池)。

2. 享元模式 vs 池化技术(对象池、连接池)

  • 核心目的不同:
    • 享元模式:强调 “共享状态的复用”,对象的属性分为共享和非共享,核心是 “数据复用”;
    • 池化技术:强调 “对象本身的复用”,对象的属性通常是固定的,核心是 “对象复用”(避免频繁创建 / 销毁对象的开销)。
  • 关系:两者常结合使用(如示例 3 的日志对象池),享元模式提供共享状态的复用,池化技术提供对象的管理和复用。

3. 享元模式 vs 原型模式

  • 核心目的不同:
    • 享元模式:复用已有对象的共享状态,减少内存占用;
    • 原型模式:通过复制现有对象创建新对象,减少对象创建的开销(适用于对象创建成本高的场景)。
  • 实现方式不同:
    • 享元模式:通过工厂获取已有对象,直接复用;
    • 原型模式:通过克隆(浅克隆 / 深克隆)创建新对象,新对象与原对象是独立的。

八、总结:享元模式的核心价值

享元模式的本质是 “共享复用”—— 它不是要消除对象,而是要让 “一个对象发挥多个对象的作用”,核心价值在于 “以空间换时间”(通过共享状态节省内存空间,通过减少对象创建提升运行时间性能)。

学习享元模式的关键,不是死记硬背角色定义,而是学会 “拆分对象的状态”:

  1. 识别哪些状态是共享的(内部状态):必须是只读、不随环境变化的属性;
  2. 识别哪些状态是非共享的(外部状态):随环境变化、每个对象独有的属性;
  3. 通过享元工厂管理享元对象,确保复用。

最后给大家一个落地建议:

  1. 先判断场景是否符合 “大量重复对象 + 高共享状态占比”,再决定是否使用享元模式;
  2. 设计时优先使用智能指针管理享元对象,避免内存泄漏;
  3. 多线程环境下务必处理线程安全问题,确保享元对象的共享状态不被篡改;
  4. 结合工厂模式、池化技术优化享元对象的创建和管理,提升实用性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值