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

享元模式 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 原则)
	核心目标是复用相同内部状态的对象,避免重复创建,减少代码冗余和内存开销,
	是复用原则的典型体现。
相关推荐
莫逸风14 小时前
【AgentScope 2.0】 0. 学习指南
java·llm·agent·agentscope
z1234567898614 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
yaoxin52112315 小时前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python
做个文艺程序员15 小时前
Linux第24篇:Java应用监控体系搭建:Prometheus+Grafana可视化运维
java·grafana·prometheus
小钻风336616 小时前
Spring Boot 文件上传详解:深入理解 MultipartFile 的使用与原理
java·开发语言
其美杰布-富贵-李16 小时前
Spring Boot 工程开发全流程说明
java·spring boot·后端
前端双越老师18 小时前
如何以前端视角(非0基础)学 Java ?
java·node.js·全栈
dear_bi_MyOnly18 小时前
【SpringBoot配置文件】
java·spring boot·后端·学习·spring·java-ee·学习方法
做个文艺程序员18 小时前
Linux第22篇:用Docker容器化你的Java SaaS应用:一次构建,随处运行
java·docker·容器
其美杰布-富贵-李19 小时前
Spring Boot 依赖注入说明文档
java·spring boot·python