设计模式之享元模式

1. 概述

享元模式 是一种结构型设计模式,它通过共享细粒度的对象来有效地支持大量对象。 享元模式尝试重用现有对象,而不是创建新的对象。通过共享,享元模式可以减少内存使用,提高性能。

2. 模式意图

  • 减少对象数量: 通过共享对象来减少应用程序中的对象数量,从而减少内存占用。
  • 提高性能: 通过重用现有对象来避免创建新对象的开销,从而提高性能。
  • 分离内部状态和外部状态: 将对象的状态分为内部状态(可以共享)和外部状态(需要在使用时传入),从而使对象可以被共享。

3. 模式结构

享元模式主要包含以下角色:

  • 享元接口 (Flyweight):
    • 定义了享元对象需要实现的接口。
    • 通常包含一个或多个方法,用于操作享元对象的外部状态。
  • 具体享元 (Concrete Flyweight):
    • 实现了享元接口。
    • 存储享元对象的内部状态。
    • 是可共享的对象。
  • 非共享具体享元 (Unshared Concrete Flyweight): (可选)
    • 实现了享元接口。
    • 存储享元对象的内部状态。
    • 不可共享的对象。
  • 享元工厂 (Flyweight Factory):
    • 负责创建和管理享元对象。
    • 维护一个享元池(例如,一个 Map),用于存储已经创建的享元对象。
    • 当客户端请求一个享元对象时,工厂首先检查享元池中是否已经存在该对象。 如果存在,则直接返回该对象;否则,创建一个新的对象并将其添加到享元池中。
  • 客户端 (Client):
    • 通过享元工厂来获取享元对象。
    • 负责传递享元对象的外部状态。

4. 适用场景

  • 应用程序中使用大量的对象。
  • 大量对象拥有相同的内部状态,可以将内部状态共享。
  • 对象的外部状态可以与内部状态分离。
  • 通过共享对象可以显著减少内存占用。

5. 优缺点

优点:

  • 减少内存占用: 通过共享对象来减少应用程序中的对象数量,从而减少内存占用。
  • 提高性能: 通过重用现有对象来避免创建新对象的开销,从而提高性能。

缺点:

  • 增加了系统的复杂性: 需要分离内部状态和外部状态,并创建享元工厂来管理享元对象,增加了系统的复杂性。
  • 读取享元状态的开销: 如果外部状态需要频繁地传递给享元对象,可能会导致额外的性能开销。
  • 线程安全问题: 需要考虑多线程环境下享元对象的线程安全问题。

6. 代码示例

场景:
假设你正在开发一个文本编辑器。 在文本编辑器中,可能会有很多重复的字符,例如,字母 “a”、数字 “1” 等。 如果为每个字符都创建一个对象,会占用大量的内存。 使用享元模式,你可以将这些重复的字符对象共享,从而减少内存的占用。

1. 享元接口 (Flyweight Interface)

1
2
3
4
// 享元接口,定义了字符的通用操作
interface CharacterFlyweight {
void display(int x, int y);
}

2. 具体享元 (Concrete Flyweight)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 具体享元,表示具体的字符
class ConcreteCharacter implements CharacterFlyweight {
private char character;

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

@Override
public void display(int x, int y) {
System.out.println("字符: " + character + " (位置: " + x + ", " + y + ")");
}
}

3. 享元工厂 (Flyweight Factory)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 享元工厂,用于创建和管理享元对象
import java.util.HashMap;
import java.util.Map;

class CharacterFactory {
private Map<Character, CharacterFlyweight> characterMap = new HashMap<>();

public CharacterFlyweight getCharacter(char character) {
if (!characterMap.containsKey(character)) {
characterMap.put(character, new ConcreteCharacter(character));
System.out.println("创建字符: " + character);
}
return characterMap.get(character);
}
}

4. 客户端 (Client)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 客户端
public class FlyweightPatternDemo {
public static void main(String[] args) {
CharacterFactory factory = new CharacterFactory();

// 使用享元对象
factory.getCharacter('A').display(10, 20);
factory.getCharacter('B').display(30, 40);
factory.getCharacter('A').display(50, 60); // 重复使用 'A'
factory.getCharacter('C').display(70, 80);
factory.getCharacter('B').display(90, 100); // 重复使用 'B'
}
}

7. 关键点说明:

  • CharacterFlyweight 接口: 定义了 display() 方法,表示字符的通用操作。 这就是**享元接口 (Flyweight Interface)**。
  • ConcreteCharacter 类: 实现了 CharacterFlyweight 接口,表示具体的字符。 它包含一个 character 字段,用于存储字符的值。 这就是**具体享元 (Concrete Flyweight)**。
  • CharacterFactory 类: 用于创建和管理享元对象。 它维护一个 characterMap,用于存储已经创建的字符对象。 当客户端请求一个字符对象时,工厂首先检查 characterMap 中是否已经存在该对象。 如果存在,则直接返回该对象;否则,创建一个新的对象并将其添加到 characterMap 中。 这就是**享元工厂 (Flyweight Factory)**。
  • FlyweightPatternDemo 类: 客户端代码,演示了如何使用享元模式来共享字符对象。

8. 运行结果:

1
2
3
4
5
6
7
8
创建字符: A
字符: A (位置: 10, 20)
创建字符: B
字符: B (位置: 30, 40)
字符: A (位置: 50, 60)
创建字符: C
字符: C (位置: 70, 80)
字符: B (位置: 90, 100)

9. 总结

享元模式 通过共享细粒度的对象,从而减少内存的占用。 享元模式适用于当应用程序中存在大量的重复对象,并且这些对象的状态可以分离成内部状态和外部状态时。 内部状态可以被共享,而外部状态需要在每次使用对象时传入。 合理使用享元模式可以减少内存占用,提高应用程序的性能。