Skip to content

享元模式

享元模式(Flyweight Pattern)属于结构型模式的范畴。该模式的主要目标是在不损害对象可变性的前提下,通过共享技术来大幅度减少对象的创建,从而降低系统内存的占用,提升程序运行效率。

DP

享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象

享元模式的核心在于“共享”和“细粒度”。它通过将对象分为内部状态(Intrinsic State)和外部状态(Extrinsic State),使得具有相同内部状态的对象可以共享,而外部状态则由客户端控制或传递给享元对象,以便在运行时动态绑定。

类图

享元模式

角色组成

  • Flyweight(享元类): 定义享元对象接口,声明一个用于存储内部状态的数据结构(如果有的话)。享元对象的内部状态必须是不可变的,或者在对象被共享期间不会改变。
  • ConcreteFlyweight(具体享元类): 实现Flyweight接口,为内部状态提供具体的共享数据。如果享元有内部状态的话,这些状态在这个类中定义。
  • Flyweight Factory(享元工厂): 创建并管理享元对象。确保享元对象可以被正确地共享。当客户端请求一个享元对象时,工厂会检查现有的享元对象池,如果已经存在一个具有相同内部状态的享元对象,则直接返回这个已存在的对象,否则创建一个新的享元对象并加入到池中。
  • Client(客户端): 维护享元对象的外部状态,并在适当的时候将外部状态传递给享元对象。客户端不直接操控享元对象的内部状态。
  • UnSharedConcreteFlyweight:不是标准享元模式定义中的一个典型角色,但它可以视作享元模式讨论中的一个补充概念,尤其是在讨论模式的适用范围和边界时。它代表那些不被共享的享元对象。

内部状态与外部状态

  • 内部状态:存储在享元对象内部,不会随环境变化而变化,可以被共享。
  • 外部状态:随环境改变而改变,不由享元对象维护,通常由客户端传入享元对象的方法中,作为参数使用。(网站的用户账号)

应用场景

《大话设计模式》

如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销就应该考虑使用;

对象的大多数状态可以是外部状态,如果删除了对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以靠考虑;

享元模式特别适用于以下场景:

  • 当一个应用程序使用大量相同或相似的对象,且这些对象大部分状态都可以共享时。
  • 对象的创建成本较高,如数据库操作、网络请求等,通过共享可以减少这类操作。
  • 系统内存资源有限,需要尽量减少对象的数量以节约内存。

优缺点

优点:

  • 减少了对象的创建,节省内存空间。
  • 提高了系统的性能,尤其是对于大量细粒度对象的场景。
  • 通过外部状态的传递,增加了对象的灵活性。

缺点:

  • 增加了系统的复杂性,需要区分内部状态和外部状态,并管理享元对象池。
  • 如果对象的内部状态过于复杂,可能会导致享元模式难以实施。
  • 不适用于所有场景,特别是当对象的内部状态频繁变化时,不适合使用享元模式

示例

创建一个文本字符的享元模式,其中字符是享元对象,字符的颜色和大小是外部状态,字符本身则是内部状态。

享元接口 (Character)

java
public interface Character {
    void display(String color, int size);
}

具体享元类 (CharacterImpl)

java
public class CharacterImpl implements Character {
    private char character; // 内部状态:字符本身

    public CharacterImpl(char character) {
        this.character = character;
    }

    @Override
    public void display(String color, int size) {
        System.out.printf("Displaying character '%c' with color '%s' and size %d%n", character, color, size);
    }
}

享元工厂 (CharacterFactory)

java
import java.util.HashMap;
import java.util.Map;

public class CharacterFactory {
    private Map<Character, Character> pool = new HashMap<>();

    public Character getCharacter(char ch) {
        Character character = pool.get(ch);
        if (character == null) {
            character = new CharacterImpl(ch);
            pool.put(ch, character);
            System.out.println("Creating and storing character: " + ch);
        }
        return character;
    }
}

客户端

java
public class Main {
    public static void main(String[] args) {
        CharacterFactory factory = new CharacterFactory();

        // 获取享元对象并设置外部状态
        Character a = factory.getCharacter('A');
        a.display("Red", 12);

        Character b = factory.getCharacter('B');
        b.display("Blue", 14);

        // 再次获取相同的字符,应复用已有对象
        Character aAgain = factory.getCharacter('A');
        aAgain.display("Green", 16);
    }
}

Released under the MIT License.