设计模式与性能优化:享元模式减少内存,代理模式延迟加载,策略模式避免条件分支

性能优化,说白了就是跟资源较劲。内存、CPU、加载时间,哪个不是我们天天要打交道的?我做了这么多年架构,发现一个有意思的现象:很多性能问题,其实不是算法不够快,而是设计上埋了坑。今天聊的三个模式——享元、代理、策略,恰好对应了三种最常见的性能痛点。

享元模式:别让对象撑爆你的内存

先说说享元模式。这个模式的名字挺文艺,但本质很朴素:共享细粒度对象,避免重复创建

我记得有一次接手一个游戏后端项目,玩家创建角色时,系统要为每个角色生成一套装备属性模板。几万个玩家同时在线,内存直接飙到 2GB 多。一查才发现,同样的装备属性被反复实例化——每个玩家手里的「铁剑」都是独立对象。

享元模式怎么解决?把对象拆成两部分:

  • 内部状态(Intrinsic State):可共享的、不变的部分。比如装备的ID、基础攻击力、图标路径。
  • 外部状态(Extrinsic State):依赖上下文的、可变的部分。比如装备的当前耐久度、强化等级。

看个简化代码:

// 享元对象:装备模板
class EquipmentFlyweight {
public:
    EquipmentFlyweight(int id, const string& name, int baseAtk)
        : id_(id), name_(name), baseAtk_(baseAtk) {}

    void display(int durability, int level) const {
        // 内部状态 + 外部状态组合输出
        cout << name_ << " 攻击力:" << baseAtk_
             << " 耐久:" << durability
             << " 强化:" << level << endl;
    }

private:
    int id_;
    string name_;
    int baseAtk_;
};

// 享元工厂
class EquipmentFactory {
public:
    shared_ptr<EquipmentFlyweight> getFlyweight(int id) {
        auto it = pool_.find(id);
        if (it != pool_.end()) {
            return it->second;
        }
        // 实际项目中从配置表加载
        auto fw = make_shared<EquipmentFlyweight>(id, "铁剑", 10);
        pool_[id] = fw;
        return fw;
    }

private:
    unordered_map<int, shared_ptr<EquipmentFlyweight>> pool_;
};

核心要点:享元模式适合大量相似对象的场景。但别滥用——如果对象本身差异很大,共享反而增加复杂度。

我的习惯:在写工厂类时,我会先问自己:「这个对象有多少属性是全局不变的?」如果超过一半,就值得用享元。

代理模式:别让大对象拖慢启动速度

代理模式,很多人只记得它做访问控制。其实它在性能优化上有个杀手级应用:延迟加载(Lazy Loading)

我曾经维护过一个报表系统,每次启动都要加载所有数据源配置。有些数据源是历史归档,一个月才用一次。结果每次重启服务都要等 40 秒。后来用代理模式改造,只有真正查询某个数据源时,才去加载它的配置。

代理模式的结构很简单:

// 抽象主题
class DataSource {
public:
    virtual ~DataSource() = default;
    virtual void query(const string& sql) = 0;
};

// 真实主题:加载代价高
class RealDataSource : public DataSource {
public:
    RealDataSource(const string& config) {
        // 模拟耗时加载
        this_thread::sleep_for(chrono::seconds(3));
        cout << "数据源加载完成" << endl;
    }

    void query(const string& sql) override {
        cout << "执行查询: " << sql << endl;
    }
};

// 代理:延迟加载
class DataSourceProxy : public DataSource {
public:
    DataSourceProxy(const string& config) : config_(config), real_(nullptr) {}

    void query(const string& sql) override {
        // 第一次使用时才真正加载
        if (!real_) {
            real_ = make_unique<RealDataSource>(config_);
        }
        real_->query(sql);
    }

private:
    string config_;
    unique_ptr<RealDataSource> real_;
};

我曾经踩过的坑:代理模式如果用在多线程环境,一定要加锁或使用 double-checked locking。否则两个线程同时触发加载,会创建两个真实对象。

你想想看,代理模式本质上是在「做不做」和「什么时候做」之间做权衡。它不减少总工作量,但把工作推迟到真正需要的时候。这对启动性能、响应速度的提升非常明显。

策略模式:干掉又臭又长的 if-else

策略模式跟性能有什么关系?关系大了。条件分支越多,CPU 分支预测失败的代价就越高。而且代码一长,维护成本直线上升。

我见过最夸张的一段代码,一个函数里嵌套了 7 层 if-else,处理 20 多种支付方式。每次加新支付渠道,都要改这个函数,改完还要全量回归测试。

策略模式的做法:把每个分支封装成独立的策略类,通过多态来消除条件判断。

// 策略接口
class PaymentStrategy {
public:
    virtual ~PaymentStrategy() = default;
    virtual void pay(double amount) = 0;
};

// 具体策略
class AlipayStrategy : public PaymentStrategy {
public:
    void pay(double amount) override {
        cout << "支付宝支付: " << amount << endl;
    }
};

class WechatStrategy : public PaymentStrategy {
public:
    void pay(double amount) override {
        cout << "微信支付: " << amount << endl;
    }
};

// 上下文
class PaymentContext {
public:
    void setStrategy(unique_ptr<PaymentStrategy> strategy) {
        strategy_ = move(strategy);
    }

    void executePayment(double amount) {
        if (strategy_) {
            strategy_->pay(amount);
        }
    }

private:
    unique_ptr<PaymentStrategy> strategy_;
};

性能对比:策略模式用虚函数调用替代了条件分支。虚函数调用有微小开销(一次间接跳转),但相比分支预测失败带来的流水线冲刷,这点代价几乎可以忽略。更重要的是,代码的可维护性提升了几个量级。

嗯,这里要注意:策略模式不是让你把所有 if-else 都干掉。如果只有两三个分支,而且不会频繁变化,用 if-else 反而更直接。我个人的判断标准是:当分支数量超过 5 个,或者每个分支的逻辑超过 10 行时,就该考虑策略模式了

三种模式的协同

这三种模式不是孤立的。我在一个项目中同时用过它们:

  • 享元模式:缓存策略对象的元数据,避免重复创建
  • 代理模式:延迟加载策略对象,只在第一次使用时才实例化
  • 策略模式:根据运行时条件选择不同的算法

说白了,性能优化不是堆技巧,而是理解资源的本质。享元管内存,代理管时间,策略管复杂度。把这三个维度管好了,你的架构自然就优雅了。

设计模式与性能优化三角 性能优化目标 享元模式 代理模式 策略模式 减少内存占用 延迟加载提速 消除条件分支 内存 · 时间 · 复杂度 —— 三个维度的优雅平衡

避坑指南:我曾经在享元模式里把外部状态也塞进了共享对象,结果导致一个玩家修改装备耐久,所有玩家的装备都变了。记住:内部状态必须不可变,外部状态由客户端管理。

专注资料整理