享元模式 (Flyweight Pattern)

定义:

享元模式(Flyweight Pattern)是一种结构型设计模式,用于优化性能和内存使用。它通过共享尽可能多的相似对象来减少内存占用,特别是在有大量对象时。这种模式通常用于减少应用程序中对象的数量,并在多个对象间共享尽可能多的状态。

享元模式的关键是区分内在状态(Intrinsic State)和外在状态(Extrinsic State):

  1. 内在状态(Intrinsic State)
    • 这部分状态是对象共享的。享元对象的内在状态不依赖于享元对象的上下文,即它们是不变的。这使得一个享元对象可以在不同的上下文中共享。
  2. 外在状态(Extrinsic State)
    • 这部分状态则依赖于具体的上下文,并且不能在享元对象之间共享。每个对象实例将有自己独特的外在状态。

享元模式通常涉及以下几个角色:

  • 享元接口(Flyweight):定义了享元对象的新操作,它通常接收并作用于外在状态。
  • 具体享元(Concrete Flyweight):实现享元接口,并存储内在状态。具体享元对象必须是可共享的,它的状态不应当随上下文变化。
  • 享元工厂(Flyweight Factory):负责创建和管理享元对象,确保合理地共享享元。

享元模式适用于程序中存在大量相似对象的情况,可以有效地减少内存占用,提高性能。典型的应用场景包括字符串常量池、数据库连接池、缓存等。

解决的问题:
  • 减少内存占用
    • 当系统中存在大量相似或相同的对象时,这些对象占用大量内存。享元模式通过共享相似对象来减少内存消耗。
  • 提高性能
    • 减少对象创建的数量意味着降低了内存占用和系统垃圾回收的负担,从而提高应用性能。
  • 管理共享对象
    • 在享元模式中,共享对象的管理变得更加集中和统一,使得对共享对象的维护和更新更加高效。
  • 优化数据结构
    • 在处理大量小粒度对象的场景中,享元模式帮助减少应用程序中的对象数量,从而优化数据结构和提高运行效率。
  • 减少系统复杂性
    • 通过减少对象实例的数量,系统变得更加简单,易于理解和维护。

享元模式通过区分内在状态和外在状态,并共享内在状态,解决了大量对象存在时的性能和内存问题。这种模式在诸如文本处理、图形渲染、数据库连接池等需要大量小粒度对象的场景中尤为有用。

使用场景:
  • 大量相似对象的场景
    • 当应用程序需要创建大量相似的对象,并且这些对象的大部分状态可以被共享时,使用享元模式可以显著减少内存占用。这种情况常见于处理大量小粒度对象的系统。
  • 内存限制严格的应用
    • 在资源有限或内存受限的应用程序(如移动设备或嵌入式系统)中,享元模式能有效减少内存消耗。
  • 重复对象的数据库存储
    • 当相同的数据需要在数据库中多次存储时,可以使用享元模式来共享这些数据,减少数据库的存储负担。
  • 图形相关应用
    • 在图形相关的应用程序中,例如图形编辑器或游戏,常常需要创建大量相似的图形对象,如图标、线条、字符等。享元模式能够减少这些对象的数量,优化性能和资源使用。
  • 字符串池
    • 在程序中,字符串常量池是享元模式的一个典型应用。例如,Java中的String对象实现采用享元模式来避免重复创建相同的字符串字面量。
  • 对象缓存
    • 当对象的创建和销毁成本较高时,可以将这些对象缓存起来供后续重用,这是享元模式的另一种应用场景。
  • 系统状态共享
    • 在需要在多个对象间共享某些状态(如配置信息、环境设置等)时,享元模式可以避免重复存储这些状态。

享元模式的关键在于识别哪些状态是可以共享的(内在状态),哪些状态是依赖于上下文的(外在状态),并且只共享内在状态。正确应用享元模式可以带来显著的性能提升和内存使用优化。

示例代码 1 - 简单享元模式:
// 享元接口
public interface Flyweight {
    void operation(String extrinsicState);
}

// 具体享元类
class ConcreteFlyweight implements Flyweight {
    private String intrinsicState;

    public ConcreteFlyweight(String intrinsicState) {
        this.intrinsicState = intrinsicState;
    }

    @Override
    public void operation(String extrinsicState) {
        System.out.println("Intrinsic State = " + intrinsicState +
                           ", Extrinsic State = " + extrinsicState);
    }
}

// 享元工厂
class FlyweightFactory {
    private Map<String, Flyweight> flyweights = new HashMap<>();

    public Flyweight getFlyweight(String key) {
        if (!flyweights.containsKey(key)) {
            flyweights.put(key, new ConcreteFlyweight(key));
        }
        return flyweights.get(key);
    }
}

// 客户端代码展示了如何使用享元模式
public class FlyweightPatternDemo {
    public static void main(String[] args) {
        FlyweightFactory factory = new FlyweightFactory();

        Flyweight flyweight1 = factory.getFlyweight("Key1");
        flyweight1.doOperation("Operation1");

        Flyweight flyweight2 = factory.getFlyweight("Key2");
        flyweight2.doOperation("Operation2");

        Flyweight flyweight3 = factory.getFlyweight("Key1");
        flyweight3.doOperation("Operation3");
    }
}

在此示例中,FlyweightFactory 管理着享元对象的集合。当客户端请求一个享元时,工厂首先检查是否已经创建了具有相应内在状态的享元对象。如果是,工厂返回现有的对象;如果不是,工厂将创建一个新的享元对象。这种方式确保具有相同内在状态的享元对象在系统中只有一个实例,从而实现对象的共享。

此示例中,ConcreteFlyweight 类的实例是根据内在状态(intrinsicState)共享的。客户端通过传递外在状态(extrinsicState)给享元对象的方法,实现了对状态的操作。这种分离内在状态和外在状态的方法是享元模式的核心,它允许系统有效地共享对象,减少内存占用,提高性能。

示例代码 2 - 享元模式在文本格式化中的应用:
// 字符享元
class CharacterFlyweight {
    private final char intrinsicChar;

    public CharacterFlyweight(char intrinsicChar) {
        this.intrinsicChar = intrinsicChar;
    }

    public void display(int fontSize) {
        System.out.println("Character: " + intrinsicChar + ", Font size: " + fontSize);
    }
}

// 享元工厂
class CharacterFactory {
    private final Map<Character, CharacterFlyweight> pool = new HashMap<>();

    public CharacterFlyweight getCharacter(char ch) {
        CharacterFlyweight flyweight = pool.get(ch);
        if (flyweight == null) {
            flyweight = new CharacterFlyweight(ch);
            pool.put(ch, flyweight);
        }
        return flyweight;
    }
}
主要符合的设计原则:
  1. 开闭原则(Open-Closed Principle):
    • 享元模式支持在不修改现有代码的情况下,增加新的享元实现。可以扩展享元工厂以支持新类型的享元对象,而无需修改现有的享元类和客户端代码。
  2. 单一职责原则(Single Responsibility Principle):
    • 享元模式中的享元对象专注于实现内在状态的行为,而享元工厂则专注于管理和创建享元对象。这样的分工使每个类只有一个原因引起变化,符合单一职责原则。
  3. 最少知识原则(Principle of Least Knowledge)/迪米特法则(Law of Demeter):
    • 享元模式鼓励使用最少的知识去完成任务,客户端不需要知道享元对象的内部结构和存储方式,只需通过享元工厂来获取所需的享元对象。

享元模式通过有效地共享对象来优化内存和性能,特别适用于大量类似对象频繁创建和销毁的场景。通过区分内在状态和外在状态,享元模式确保共享对象的高效管理。

在JDK中的应用:
  • 字符串常量池(String Constant Pool):
    • 在Java中,字符串常量池是享元模式的一个典型例子。相同的字符串字面量只存储一次。当创建相同内容的字符串时,Java会首先检查字符串常量池中是否存在该字符串,如果存在,则返回对池中字符串的引用,而不是创建一个新的对象。
  • 包装类型的值缓存:
    • Java的自动装箱过程使用享元模式来缓存一定范围内的包装对象。例如,Integer.valueOf() 方法会缓存 -128127 之间的 Integer 对象。当在这个范围内创建Integer对象时,会直接返回缓存中的对象而不是每次都创建新对象。
  • java.awt.Font:
    • AWT库中的 Font 类使用享元模式来减少创建字体实例的内存开销。由于字体创建代价较大,共享相同属性的字体实例可以显著提高性能和减少内存消耗。

这些应用展示了享元模式在Java标准库中的普遍性和实用性。特别是在处理大量细粒度对象时,通过共享对象来优化内存和性能,从而提升应用的效率和响应速度。

在Spring中的应用:
  • Spring Bean的作用域
    • 在Spring框架中,单例模式是Bean默认的作用域。虽然这是单例模式的应用,但它也体现了享元模式的核心思想 —— 对象共享。单例Bean在Spring容器中只创建一次,之后每次请求都使用相同的实例,从而减少了对象创建的开销。
  • Spring Security的上下文持有者
    • Spring Security中使用的SecurityContextHolder策略,可以看作是享元模式的一个应用。它为不同的安全上下文提供统一的访问点,并在内部通过线程局部变量共享安全上下文信息。
  • 缓存抽象
    • Spring的缓存抽象允许在应用中轻松地添加和管理缓存,这也是一种享元模式的体现。缓存的目的是重用之前计算的结果,减少资源密集型操作的重复执行。
  • 数据访问对象(DAO)
    • 在Spring框架中,DAO类通常被设计为无状态的单例Bean,这意味着它们在应用中被共享。虽然这本质上是单例模式的应用,但是它也体现了享元模式的思想,即共享对象以减少内存占用和提高性能。

尽管Spring框架中享元模式的应用可能不如其他设计模式那样显著,但在处理对象共享和重用方面,享元模式的思想仍然在Spring的设计和实现中发挥着作用。


你可能感兴趣的:(设计模式,享元模式,java,设计模式)