享元 ( Flyweight ) 模式

享元模式(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<Character, TextCharacter> 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’,并且通过比较 charAcharC 的引用,可以看到它们是同一个对象的引用,表示享元模式成功共享了相同的对象。

优点

享元模式的优点包括:

  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=<max> 来调整上限),表示这个范围内的整数会被缓存起来以供重复使用。

在 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 < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 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,因为它们引用不同的对象
    }
}

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

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

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

  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
享元设计模式Flyweight Design Pattern)是一种用于优化大量对象创建和使用的设计模式。在Android开发中,使用享元模式可以有效地减少内存消耗和提高性能。 在享元模式中,对象被分为两种状态:内部状态和外部状态。内部状态是不变的,可以被多个对象共享,而外部状态是可变的,每个对象都有自己的外部状态。 在Android中,典型的例子是使用Bitmap对象来显示图片。当需要显示多个相同的图片时,可以使用享元模式来共享已加载的Bitmap对象,而不是每次都创建新的Bitmap对象。 以下是一个简单的示例代码: ```java public class BitmapFactory { private Map<String, Bitmap> bitmapCache = new HashMap<>(); public Bitmap getBitmap(String path) { Bitmap bitmap = bitmapCache.get(path); if (bitmap == null) { // 如果缓存中没有该Bitmap对象,则创建新的Bitmap对象 bitmap = BitmapFactory.decodeFile(path); bitmapCache.put(path, bitmap); } return bitmap; } } ``` 在上面的示例中,`BitmapFactory` 类使用一个 `bitmapCache` Map 来缓存已加载的 Bitmap 对象。当需要获取 Bitmap 对象时,首先从缓存中查找,如果找到则返回缓存的对象,否则创建新的 Bitmap 对象并存入缓存。 通过使用享元模式,可以避免重复创建相同的 Bitmap 对象,从而减少内存消耗。这在需要频繁加载和显示大量图片的应用中非常有用。 需要注意的是,享元模式适用于有大量相似对象的情况,并且需要权衡共享对象和创建对象的开销。在某些情况下,过度使用享元模式可能会导致代码复杂化,降低可读性和可维护性。因此,在使用享元模式时应根据实际情况进行评估和折衷。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值