一、结构型模式

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

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

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

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

 九、其他相关设计模式

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

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐