小工具知识网

当前位置: 首页 > 行业知识 > 享元 ( Flyweight ) 模式

行业知识

享元 ( Flyweight ) 模式

2026-10-31 20:31:31 来源:互联网转载

享元���式(Flyweight Pattern)是一种结构型设计模式,其主要目的是减少应用程序中相似对象的数量,从而节省内存或提高性能。这一模式的核心思想是共享对象,即将大量的相似对象中可复用的部分抽取出来,以节省系统资源。

()

结构

享元模式的关键概念包括两种类型的状态:

  1. 内部状态(Intrinsic State):这些状态是可以共享的,通常存储在享元对象内部,不随外部环境变化而变化。内部状态是享元对象的固有属性。

    ()
  2. 外部状态(Extrinsic State):这些状态是不可以共享的,通常存储在客户端代码中,并在需要时传递给享元对象。外部状态是依赖于具体场景的信息。

享元模式的主要元素包括:

  • 享元工厂(Flyweight Factory):负责创建和管理享元对象。它通常包含一个享元对象池,用于缓存已经创建的享元对象,以便复用。

  • 享元接口(Flyweight):定义了享元对象的接口,通常包括一个方法用于接受外部状态作为参数。

  • 具体享元(Concrete Flyweight):实现了享元接口,表示可以被共享的具体对象。它包含了内部状态,而外部状态在需要时从客户端传递。

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

  • 客户端(Client):使用享元工厂来获取享元对象,并在需要时传递外部状态。客户端代码不需要创建大量的相似对象,而是通过享元工厂获取共享对象。

    示例

    下面是一个使用 Java 实现享元模式的示例,假设要创建一个简单的文本编辑器,共享相同字符的享元对象以减少内存使用。

    首先定义享元接口和具体享元类:

    // 享元接口interface TextCharacter {    void display();}// 具体享元类class ConcreteTextCharacter implements TextCharacter {    private char character;    public ConcreteTextCharacter(char character) {        this.character = character;    }    @Override    public void display() {        System.out.print(character);    }}

    接下来,创建享元工厂,负责管理和提供享元对象:

    import java.util.HashMap;import java.util.Map;// 享元工厂class TextCharacterFactory {    private Map characterMap = new HashMap();    public TextCharacter getCharacter(char character) {        TextCharacter textCharacter = characterMap.get(character);        if (textCharacter == null) { // 单例模式            textCharacter = new ConcreteTextCharacter(character);            characterMap.put(character, textCharacter);        }        return textCharacter;    }}

    编写客户端代码来使用享元模式:

    public class Client {    public static void main(String[] args) {        TextCharacterFactory factory = new TextCharacterFactory();        // 创建并显示文本        TextCharacter charA = factory.getCharacter('A');        TextCharacter charB = factory.getCharacter('B');        TextCharacter charC = factory.getCharacter('A');        charA.display(); // 共享字符 'A'        charB.display(); // 共享字符 'B'        charC.display(); // 共享字符 'A',再次使用同一字符对象        // 检查是否共享相同对象        System.out.println("\ncharA == charC: " + (charA == charC)); // true,表示共享对象    }}

    在这个示例中,使用 TextCharacterFactory 来获取享元对象,该工厂根据字符创建享元对象并在需要时共享它们。客户端代码创建了三个字符对象,其中两个是相同的字符 ‘A’,所以它们共享相同的享元对象。这减少了内存使用并提高了性能。

    运行示例代码,将看到输出显示共享字符 ‘A’ 和 ‘B’,并且通过比较 charA 和 charC 的引用,可以看到它们是同一个对象的引用,表示享元模式成功共享了相同的对象。

    优点

    享元模式的优点包括:

    1. 内存优化:享元模式通过共享相似对象的内部状态,减少了对象的数量,从而节省了内存空间。这对于需要大量相似对象的应用程序来说特别有益。
    2. 性能提升:由于减少了对象的数量,创建和销毁对象的开销减小,可以提高系统的性能,尤其是在大规模应用中。
    3. 分离内外部状态:享元模式将对象的内部状态和外部状态分离,使得系统更灵活,可以适应不同的场景。内部状态由享元对象管理,外部状态由客户端管理,这两者之间解耦。
    4. 重用性:享元模式可以促使你更好地重用现有对象,而不是重复创建相似的对象。这有助于提高代码的可维护性和可扩展性。

    缺点

    然而,享元模式也有一些限制和缺点:

    1. 复杂性增加:引入享元模式会增加代码的复杂性,特别是在需要创建和管理享元对象池的情况下。这可能导致系统更难以理解和维护。
    2. 不适用于所有情况:享元模式适用于需要大量相似对象的情况。对于对象数量有限或外部状态无法分离的情况,不适合使用享元模式。
    3. 潜在的线程安全问题:如果多个线程同时访问享元对象池,并且没有适当的同步机制,可能会引发线程安全问题。
    4. 外部状态管理:客户端需要管理外部状态,并在需要时传递给享元对象。这可能会增加客户端代码的复杂性,特别是在有大量外部状态需要管理的情况下。

    使用场景

    享元模式通常在以下情况下使用,以减少内存消耗和提高性能:

    1. 大量相似对象:当应用程序需要创建大量相似对象时,例如字符、图标、按钮、粒子等,可以使用享元模式来共享这些对象的内部状态,从而减少内存占用。

    2. 对象的内部状态和外部状态:如果一个对象可以分成内部状态(不变的、可共享的部分)和外部状态(变化的、不可共享的部分),则可以使用享元模式将内部状态共享,而外部状态由客户端管理。

    3. 性能优化:当创建和销毁对象的成本很高时,例如数据库连接、线程池中的线程等,可以使用享元模式来重复使用这些资源,提高性能。

    4. 缓存管理:享元模式可以用于实现缓存管理,例如在Web应用中缓存页面片段或数据库查询结果以提高响应速度。

    5. 文本编辑器和绘图软件:在文本编辑器和绘图软件中,字符、字体、颜色、图形等都可以作为享元对象,以便在编辑大型文档或绘制复杂图形时减少内存消耗。

    6. 游戏开发:在游戏开发中,粒子系统、游戏角色、装备和道具等可以作为享元对象,以节省内存和提高性能。

    7. 网络通信:在网络通信中,可以使用享元模式来缓存已建立的连接对象,以减少连接的开销。

    8. 图形界面库:在图形界面库中,UI元素如按钮、文本框、窗口等可以作为享元对象,以减少内存占用。

    源码解析

    Integer 类是 Java 中的一个包装类,用于表示整数。它使用了享元模式来提高性能和减少内存消耗,特别是在表示小整数范围内的整数时。

    具体来说,Integer 类在内部维护了一个缓存池,这个池用于存储在一定范围内的整数对象。这个范围默认是从 -128 到 127(可以通过 Java 虚拟机参数 -Djava.lang.Integer.IntegerCache.high= 来调整上限),表示这个范围内的整数会被缓存起来以供重复使用。

    在 Java 中,整数对象的缓存范围确实是从 -128 到 127。这个范围的设定是 Java 虚拟机的一种优化策略,旨在节省内存和提高性能。以下是这个范围的具体源码和相关说明。

    在 java.lang.Integer 类中,有一个名为 IntegerCache 的内部类,用于缓存整数对象。下面是相关的源码摘录:

    private static class IntegerCache {    static final int low = -128;    static final int high;    static final Integer cache[];    static {        // high value may be configured by property        int h = 127;        String integerCacheHighPropValue =            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");        if (integerCacheHighPropValue != null) {            try {                int i = parseInt(integerCacheHighPropValue);                i = Math.max(i, 127);                // Maximum array size is Integer.MAX_VALUE                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);            } catch( NumberFormatException nfe) {                // If the property cannot be parsed into an int, ignore it.            }        }        high = h;        cache = new Integer[(high - low) + 1];        int j = low;        for(int k = 0; k = 127;    }    private IntegerCache() {}}

    在上述源码中,low 表示缓存范围的下限,固定为 -128。high 表示缓存范围的上限,可以根据系统属性 "java.lang.Integer.IntegerCache.high" 来配置,但不会超过 Integer.MAX_VALUE。默认情况下,high 值为 127。cache 数组用于存储整数对象。

    在 static 初始化块中,整数对象在指定的范围内(-128 到 127)被缓存到 cache 数组中。这些整数对象可以通过直接引用来重复使用,而不需要每次创建新的对象。

    注意,范围为 -128 到 127 的整数对象在 Java 中被要求是不可变的,并且在某些情况下需要进行对象引用的比较而不是值的比较。这是 Java 中整数对象缓存的一种优化策略,用于节省内存和提高性能。


    以下是关于 Integer 类享元模式使用的示例代码:

    public class IntegerFlyweightExample {    public static void main(String[] args) {        Integer int1 = 10;  // 从缓存池中获取整数对象        Integer int2 = 10;  // 从缓存池中获取相同整数对象        Integer int3 = 128; // 超出缓存池范围,创建新对象        Integer int4 = 128; // 超出缓存池范围,创建新对象        System.out.println(int1 == int2); // 输出 true,因为它们引用相同的对象        System.out.println(int3 == int4); // 输出 false,因为它们引用不同的对象    }}

    在上述示例中,当我们创建整数对象 int1 和 int2 时,它们的值都是 10,位于缓存池的范围内,因此它们实际上引用了相同的整数对象。而 int3 和 int4 的值为 128,超出了缓存池的范围,因此它们引用的是不同的整数对象。

    通过享元模式,Integer 类节省了内存,因为相同的整数值不需要每次都创建新的对象,而是可以重复使用已存在的对象。这对于整数值在缓存池范围内的情况尤为明显,因为它们在大多数情况下都是可共享的。

    需要注意的是,虽然享元模式可以提高性能和节省内存,但在某些情况下,由于整数对象的不可变性,可能会导致装箱和拆箱操作,需要谨慎处理性能问题。在编写代码时,开发人员应该考虑这些因素,并选择合适的数据类型来满足应用程序的需求。

上一篇:服务器怎么用证书登录?

下一篇:域名过期怎么去续费?