欢迎光临
我们一直在努力

​Java 设计模式・享元模式篇:从思想到代码实现

一、结构型模式

在面向对象的世界里,如何优雅地组织类与对象、构建更大的结构,是每一位开发者都会反复思考的问题。直接堆砌类和继承固然简单,但当业务复杂度上升、类间关系变得盘根错节时,这种方式就会让代码变得臃肿、难以维护。

结构型设计模式正是为了解决这一痛点而诞生的一套思想体系。它们关注如何将类或对象按某种布局组合成更大的结构,通过组合、代理、适配和装饰等手段,让代码更具灵活性、可复用性和可维护性。

在 Java 开发中,结构型模式主要包含以下 7 种经典实现:

  • 代理模式 (Proxy):为其他对象提供一种代理以控制对这个对象的访问,实现延迟加载、权限控制等。Java 设计模式・代理模式篇:从思想到代码实现-CSDN博客
  • 适配器模式 (Adapter):将一个类的接口转换成客户期望的另一个接口,让原本不兼容的类可以协同工作。Java 设计模式・适配器模式篇:从思想到代码实现-CSDN博客
  • 装饰器模式 (Decorator):动态地给一个对象添加额外的职责,比生成子类更灵活地扩展功能。Java 设计模式・装饰器模式篇:从思想到代码实现-CSDN博客
  • 桥接模式 (Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化,避免类爆炸。Java 设计模式・桥接模式篇:从思想到代码实现-CSDN博客
  • 外观模式 (Facade):为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。Java 设计模式・外观模式篇:从思想到代码实现-CSDN博客
  • 组合模式 (Composite):将对象组合成树形结构以表示 “部分 – 整体” 的层次关系,使客户端对单个对象和组合对象的使用具有一致性。Java 设计模式・组合模式篇:从思想到代码实现-CSDN博客
  • 享元模式 (Flyweight):运用共享技术有效地支持大量细粒度的对象,减少内存占用。
  • 二、享元模式

    2.1 介绍

    享元模式是一种结构型设计模式,它的核心定义是:运用共享技术来有效地支持大量细粒度对象的复用。就是当系统中需要创建大量相似或相同的对象时,不必为每个对象都分配独立的内存空间,而是将这些对象中相同的部分(称为 “内部状态”)提取出来共享,只为那些不同的部分(称为 “外部状态”) 保留个性化的存储空间。这样可以极大地减少内存中对象的数量,节省系统资源。

    • 内部状态(Intrinsic State)
      • 定义:对象中不会随环境改变而改变的、可共享的部分。
      • 例子:比如围棋中的棋子,“黑色”“白色” 就是内部状态(所有黑棋的颜色都是黑,所有白棋都是白,不会变)。
    • 外部状态(Extrinsic State)
      • 定义:对象中随环境改变而改变的、不可共享的部分,通常由外部代码传入。
      • 例子:围棋棋子的 “位置(x,y 坐标)” 就是外部状态(每个棋子的位置都不同,且会随下棋过程变化)。

    2.2 角色

    • 抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。

    • 具体享元(Concrete Flyweight)角色 :它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。

    • 非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。

    • 享元工厂(Flyweight Factory)角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

    三、代码实现(不含外部状态)

    为了方便理解,本文采用中文定义类名

    3.1 抽象享元角色

    public abstract class 棋子 {
    public abstract String getColor();

    public void display() {
    System.out.println("棋子颜色:" + getColor() );
    }
    }

    3.2 具体享元角色

    public class 白棋 extends 棋子{
    @Override
    public String getColor() {
    return "白色";
    }
    }
    public class 黑棋 extends 棋子{
    @Override
    public String getColor() {
    return "黑色";
    }
    }

    3.3 享元工厂角色

    public class 棋子工厂 {
    private static 棋子工厂 instance = new 棋子工厂();
    private static HashMap<String, 棋子> pool;
    private 棋子工厂() {
    pool = new HashMap<>();
    pool.put("黑色", new 黑棋());
    pool.put("白色", new 白棋());
    }

    public static 棋子工厂 getInstance() {
    return instance;
    }

    public 棋子 getChess(String color) {
    return pool.get(color);
    }
    }

    3.4 客户端

    public class 客户端 {
    public static void main(String[] args) {
    棋子工厂 factory = 棋子工厂.getInstance();
    棋子 棋子1 = factory.getChess("黑色");
    棋子 棋子2 = factory.getChess("黑色");
    棋子 棋子3 = factory.getChess("白色");
    棋子 棋子4 = factory.getChess("白色");
    System.out.println("棋子1和棋子2是否相等:" + (棋子1 == 棋子2));
    System.out.println("棋子3和棋子4是否相等:" + (棋子3 == 棋子4));
    棋子1.display();
    棋子2.display();
    棋子3.display();
    棋子4.display();
    }
    }
    棋子1和棋子2是否相等:true
    棋子3和棋子4是否相等:true
    棋子颜色:黑色
    棋子颜色:黑色
    棋子颜色:白色
    棋子颜色:白色

    Process finished with exit code 0

    四、代码实现(含外部状态)

    状态类型代码载体核心特征职责归属
    内部状态 getColor() 抽象方法 固定、可共享、子类实现 由具体享元角色(白棋/黑棋)承载和实现
    外部状态 复合棋子 compositeChess 参数(含x/y) 可变、不可共享、临时传入 由非享元角色(复合棋子)存储,客户端传入使用

    4.1 抽象享元角色

    public abstract class 棋子 {
    public abstract String getColor();

    public void display(复合棋子 compositeChess) {
    System.out.println("棋子颜色:" + getColor() + ",位置:" +
    compositeChess.getX() + "," + compositeChess.getY());
    }
    }

     

    4.2 非享元角色

    public class 复合棋子 {
    private 棋子 棋子;

    private int x;
    private int y;

    public 复合棋子(棋子 棋子, int x, int y) {
    this.棋子 = 棋子;
    this.x = x;
    this.y = y;
    }

    public void display() {
    this.棋子.display(this.x, this.y);
    }
    }

    4.3 具体享元角色,享元工厂角色不变

    4.4 客户端

    public class 客户端 {
    public static void main(String[] args) {
    棋子工厂 factory = 棋子工厂.getInstance();
    棋子 棋子1 = factory.getChess("黑色");
    棋子 棋子2 = factory.getChess("黑色");
    棋子 棋子3 = factory.getChess("白色");
    棋子 棋子4 = factory.getChess("白色");
    棋子1.display(new 复合棋子(1,1));
    棋子2.display(new 复合棋子(2,2));
    棋子3.display(new 复合棋子(3,3));
    棋子4.display(new 复合棋子(4,4));
    }
    }
    棋子颜色:黑色,位置:1,1
    棋子颜色:黑色,位置:2,2
    棋子颜色:白色,位置:3,3
    棋子颜色:白色,位置:4,4

    Process finished with exit code 0

    五、优缺点

    5.1 优点

    • 极大节省内存空间(核心优势) 享元模式的核心价值就是复用大量相似 / 相同的细粒度对象,只存储一份 “内部状态”,避免重复创建对象占用内存

    • 减少对象创建 / 销毁的性能开销 创建和销毁对象(尤其是大量细粒度对象)会消耗 CPU 资源,享元模式通过复用已有对象,避免了频繁的对象创建 / 销毁操作,提升系统性能。

    • 提高系统的可维护性将 “内部状态”(固定、可共享)和 “外部状态”(可变、不可共享)分离,职责边界清晰:

      • 内部状态的修改只需改共享对象,无需修改所有相关对象;
      • 外部状态由客户端管理,灵活调整,不影响共享对象的核心逻辑。

    5.2 缺点

    • 增加系统复杂度 为了实现共享,需要额外引入 “享元工厂” 来管理对象池,还要区分 “内部状态” 和 “外部状态”,增加了类的数量和代码逻辑的复杂度,对新手不友好。

    • 读取共享对象的开销(潜在问题)如果享元工厂的对象池过大,查找共享对象时(比如从 Map 中获取)会产生一定的查找开销,虽然通常可以忽略,但极端场景下(如百万级对象池)会影响性能。

    • 外部状态需要由客户端传入或由非享元角色存储,若外部状态过多、过复杂:

      • 会增加客户端的代码负担(需要管理大量外部状态);
      • 若外部状态和内部状态耦合,可能导致逻辑混乱,甚至出现线程安全问题(比如多线程修改同一外部状态)。

    六、使用场景

    适用场景

    系统中有大量相似 / 相同对象、对象的大部分状态可外部化、追求内存优化时(如池化场景、海量细粒度对象场景)。

    不适用场景

    对象数量少、内 / 外部状态难以分离、追求代码简洁性优先于内存优化时。

    七、对比

    7.1 与单例模式对比

     Java 设计模式・单例模式篇:从思想到代码实现-CSDN博客

    维度享元模式单例模式
    核心目标 复用多个相似对象,减少对象数量 保证某个类只有一个实例,全局唯一
    对象数量 可以有多个共享实例(如黑棋、白棋) 只能有 1 个实例
    共享范围 按 “内部状态” 分组共享(颜色相同的棋子共享) 全系统共享同一个实例
    典型例子 围棋的黑棋 / 白棋、字体对象 配置管理器、日志工厂

    7.2 与原型模式对比

     Java 设计模式・原型模式篇:从思想到代码实现-CSDN博客

    维度享元模式原型模式
    核心目标 复用已有对象,避免重复创建 快速创建新对象(克隆),减少创建开销
    对象状态 区分内部 / 外部状态,内部状态共享 克隆出的对象是独立实例,状态可自定义
    内存策略 减少对象数量,节省内存 不减少对象数量,只提升创建效率

    7.3 与装饰者模式对比

     Java 设计模式・装饰器模式篇:从思想到代码实现-CSDN博客

    维度享元模式装饰器模式
    核心目标 共享对象,节省内存 动态扩展对象功能,不改变核心结构
    对象关系 多个场景复用同一个对象 装饰器包装原对象,形成 “对象链”
    状态处理 固化内部状态,外部状态动态传入 不改变原对象状态,只增加行为

    八、源码举例 Integer

    @jdk.internal.ValueBased
    public final class Integer extends Number
    implements Comparable<Integer>, Constable, ConstantDesc {

    private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer[] cache;
    static Integer[] archivedCache;

    static {
    // high value may be configured by property
    int h = 127;
    String integerCacheHighPropValue =
    VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    if (integerCacheHighPropValue != null) {
    try {
    h = Math.max(parseInt(integerCacheHighPropValue), 127);
    // Maximum array size is Integer.MAX_VALUE
    h = Math.min(h, Integer.MAX_VALUE – (-low) -1);
    } catch( NumberFormatException nfe) {
    // If the property cannot be parsed into an int, ignore it.
    }
    }
    high = h;

    // Load IntegerCache.archivedCache from archive, if possible
    CDS.initializeFromArchive(IntegerCache.class);
    int size = (high – low) + 1;

    // Use the archived cache if it exists and is large enough
    if (archivedCache == null || size > archivedCache.length) {
    Integer[] c = new Integer[size];
    int j = low;
    for(int i = 0; i < c.length; i++) {
    c[i] = new Integer(j++);
    }
    archivedCache = c;
    }
    cache = archivedCache;
    // range [-128, 127] must be interned (JLS7 5.1.7)
    assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
    }

    }

    • nteger 类内置了IntegerCache(享元工厂),缓存了 -128 ~ 127 范围内的 Integer 对象;
    • 当通过Integer.valueOf(int)获取对象时,优先从缓存中取共享对象,超出范围才新建。

    public class IntegerFlyweightDemo {
    public static void main(String[] args) {
    // 127在缓存范围内,复用同一个对象(享元)
    Integer a = Integer.valueOf(127);
    Integer b = Integer.valueOf(127);
    System.out.println(a == b); // true(同一对象)

    // 128超出缓存范围,创建新对象
    Integer c = Integer.valueOf(128);
    Integer d = Integer.valueOf(128);
    System.out.println(c == d); // false(不同对象)

    // 注意:new Integer()会强制新建,不走享元
    Integer e = new Integer(127);
    System.out.println(a == e); // false
    }
    }

     九、其他相关设计模式

    9.1 单例模式

    Java 设计模式・单例模式篇:从思想到代码实现-CSDN博客

    9.2 简单工厂+工厂方法+抽象工厂模式

    Java 设计模式・工厂模式篇:从思想到代码实现-CSDN博客

    9.3 建造者模式

    Java 设计模式・建造者模式篇:从思想到代码实现-CSDN博客

    9.4 原型模式

    Java 设计模式・原型模式篇:从思想到代码实现-CSDN博客

    9.5 代理模式

    Java 设计模式・代理模式篇:从思想到代码实现-CSDN博客

    9.6 适配器模式

    Java 设计模式・适配器模式篇:从思想到代码实现-CSDN博客

    9.7 装饰器模式

    Java 设计模式・装饰器模式篇:从思想到代码实现-CSDN博客

    9.8 桥接模式

    Java 设计模式・桥接模式篇:从思想到代码实现-CSDN博客

    9.9 外观模式

    Java 设计模式・外观模式篇:从思想到代码实现-CSDN博客

    9.10 组合模式

    Java 设计模式・组合模式篇:从思想到代码实现-CSDN博客

    9.11 享元模式

    本篇

    赞(0)
    未经允许不得转载:171主机测评 » ​Java 设计模式・享元模式篇:从思想到代码实现
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址