二十三种设计模式(十一)--享元模式

享元模式 Flyweight

享元模式为了解决多个对象占用内存大的问题, 采用多个对象共享对象池中的原始对象的方式, 提高性能, 节省程序开销

享元模式, 字面意思就很恰当, 共享元素.

主要角色有两个, 一个是基于共同接口的对象类, 另一个是工厂方法用来输出对象池中的对象

共享的元素必须是对象实例化后成员始终不变的, 所有会在运行时会变化的成员都要通过传参的形式从外部注入, 不能预先定义在享元类中.

java 复制代码
interface BikeInterface {
    void run();
}

// 具体享元类(封装内部状态,私有化并提供访问方法)
class ConcreteBike implements BikeInterface {
    // 内部状态:可共享、不可变
    private final String brand; // 私有化+final,确保不可变

    // 构造方法初始化内部状态
    public ConcreteBike(String brand) {
        this.brand = brand;
    }

    public String getBrand() {
        return brand;
    }

    @Override
    public void run() {
        System.out.println(brand +"牌自行车");
    }
}

// 享元工厂, 建立对象存储池, 池内对象不重复
class BikeFlyweightFactory  {

    private Map<String, BikeInterface> bikePool = new HashMap<>();
    
    // 获取对象时, 相同的对象直接从共享池中取出
    public ConcreteBike getBike(String brand) {
        if (!bikePool.containsKey(brand)) {
            bikePool.put(brand, new ConcreteBike(brand));
        }

        return (ConcreteBike)bikePool.get(brand);
    }

    public void size() {
        System.out.println("size = " + bikePool.size());
    }
}

调用测试:

java 复制代码
public class FlyweightPattern {
    public static void main(String[] args) {
        BikeFlyweightFactory bf = new BikeFlyweightFactory();
        ConcreteBike a = bf.getBike("飞鸽");
        ConcreteBike b = bf.getBike("上海");
        ConcreteBike c = bf.getBike("飞鸽");

        bf.size();

        // 这里对象a和c共享一个对象, 节省了开销
        a.run();
        b.run();
        c.run();

    }
}

运行结果

复制代码
size = 2
飞鸽牌自行车
上海牌自行车
飞鸽牌自行车

将不可变的内部状态抽离并共享,减少对象实例化的数量,这是享元模式最核心的价值。

注意要点:

  1. 内部状态必须保证不可变
    这是享元模式的第一铁律:内部状态若可变,一个客户端修改状态会导致所有共享该对象的客户端受影响,引发逻辑混乱。
  2. 外部状态由客户端持有,享元对象不持有外部状态的引用(避免内存泄漏或状态耦合),仅在执行方法时临时使用。
    若外部状态较多,可封装为独立的外部状态对象,而非零散的方法参数,提升代码可读性
  3. 享元池的选型与性能权衡
    避免使用ArrayList等需要遍历的集合存储享元对象(时间复杂度O(n)),推荐使用HashMap/ConcurrentHashMap(以内部状态为 key,时间复杂度O(1)),尤其在对象数量较多时。
  4. 注意享元池的内存上限:若共享对象过多,可能导致享元池占用内存过大,可结合缓存淘汰策略(如 LRU)限制池的大小。

享元模式与线程池、数据库连接池容易混淆,二者是相似但不同的概念:

享元模式:聚焦于对象的内部状态共享,对象是无状态 / 不可变的,共享的是 "对象本身"。

池化技术:聚焦于对象的复用,对象是有状态的(如线程的执行状态、连接的占用状态),池化的是 "对象的使用权"(用完后归还池,而非永久共享)。

二者的共同思想是 "减少对象创建",但享元模式是逻辑层面的状态共享,池化技术是物理层面的对象复用。

享元模式遵循的设计原则

复制代码
单一职责原则
	享元对象:仅负责封装内部状态并实现自身行为,不处理外部状态的管理。
	享元工厂:仅负责创建和复用享元对象,不参与享元对象的业务逻辑。
	外部状态:由客户端或独立的外部状态类管理,与享元对象解耦。
开闭原则
	新增具体享元类(如新增ElectricBike实现BikeInterface)时,
	无需修改享元工厂的核心逻辑(只需工厂能识别新的内部状态 key),符合 "对扩展开放、对修改关闭"。
里氏替换原则
	所有具体享元类都实现抽象享元接口,客户端可使用具体享元对象替换抽象享元对象,
	而不影响程序逻辑(如ConcreteBike替换BikeInterface)。
迪米特法则(LoD)
	客户端仅需与享元工厂交互获取享元对象,无需了解享元池的内部实现(如HashMap的存储逻辑),
	降低了客户端与工厂的耦合。
复用原则(DRY 原则)
	核心目标是复用相同内部状态的对象,避免重复创建,减少代码冗余和内存开销,
	是复用原则的典型体现。
相关推荐
じ☆冷颜〃3 小时前
分布式系统中网络技术的演进与异构融合架构(HFNA)
笔记·python·物联网·设计模式·架构·云计算
Wang15306 小时前
jdk内存配置优化
java·计算机网络
0和1的舞者6 小时前
Spring AOP详解(一)
java·开发语言·前端·spring·aop·面向切面
Wang15306 小时前
Java多线程死锁排查
java·计算机网络
小小星球之旅7 小时前
CompletableFuture学习
java·开发语言·学习
jiayong237 小时前
知识库概念与核心价值01
java·人工智能·spring·知识库
皮皮林5518 小时前
告别 OOM:EasyExcel 百万数据导出最佳实践(附开箱即用增强工具类)
java
Da Da 泓8 小时前
多线程(七)【线程池】
java·开发语言·线程池·多线程
To Be Clean Coder8 小时前
【Spring源码】getBean源码实战(三)
java·mysql·spring
Wokoo79 小时前
开发者AI大模型学习与接入指南
java·人工智能·学习·架构