设计模式 享元模式

本文将介绍设计模式中的享元模式。

一、什么是享元模式?

如果一个软件系统在运行时所创建的相同或相似对象数量太多,将导致运行代价过高,带来系统资源浪费、性能下降等问题。享元模式正是用于解决这一问题的,它通过共享技术实现相同或相似对象的复用。

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

二、享元池

在享元模式中,存储共享实例对象的地方被称为享元池。

三、内部状态和外部状态

要实现相同对象的复用非常简单,只需要将对象存入享元池,需要时取出即可。

要做到相似对象的复用,首先便要对这些对象的相同部分和相似部分做抽象,在享元模式中,抽象为内部状态和外部状态,具体如下:

  • 内部状态:
    • 存储在享元对象内部
    • 不会随环境改变而改变
    • 可共享
    • 简单来说,内部状态就是相同部分
  • 外部状态:
    • 通常由调用者保存,在享元对象需要使用时再传入
    • 随环境改变而改变
    • 不可共享
    • 简单来说,外部状态就是相似部分

通过内部状态和外部状态的区分,可以实现相似对象的复用。

四、实现

1. 结构

2. 角色

  • Flyweight 抽象享元类:
    • 通常是接口或抽象类
    • 声明了具体享元类公共的方法,包括:
      • 获取内部状态
      • 设置外部状态
  • ConcreteFlyweight 具体享元类:
    • 实现了 Flyweight 抽象享元类
    • 通常结合单例模式,使得每一个 ConcreteFlyweight 具体享元类都只有一个唯一的实例化对象
  • UnsharedConcreteFlyweight 非共享具体享元类:
    • 并不是所有 Flyweight 抽象享元类的子类都需要被共享,不能被共享的子类可以设计为 UnsharedConcreteFlyweight 非共享具体享元类
    • 当需要 UnsharedConcreteFlyweight 非共享具体享元类的实例化对象时,直接通过实例化创建
  • FlyweightFactory 享元工厂类:
    • 用于创建和管理享元对象
    • 它针对抽象享元类编程
    • 将各种类型的具体享元对象存储在一个享元池中
    • 当用户请求一个具体享元对象时,
      • 如果享元池中已存储有实例,则直接返回
      • 否则,创建实例,放入享元池,返回

3. 简单示例

Flyweight:

1
2
3
public abstract class Flyweight {
public abstract void operation();
}

ConcreteFlyweight:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ConcreteFlyweight extends Flyweight {
/**
* 内部状态
* <p>维护在对象内部的成员变量中,同一个享元对象的内部状态是一致的
*/
private String intrinsicState;

/**
* 操作
* <p>在进行操作时,由调用者传入外部状态
* <p>使得同一个对象可以随环境的改变呈现出不同的状态
*/
public void operation(String extrinsicState) {
// ···
}
}

UnsharedConcreteFlyweight:

1
2
3
4
5
6
7
8
9
10
public class UnsharedConcreteFlyweight extends Flyweight {
/**
* 操作
* <p>在进行操作时,由调用者传入外部状态
* <p>使得同一个对象可以随环境的改变呈现出不同的状态
*/
public void operation(String extrinsicState) {
// ···
}
}

FlyweightFactory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class FlyweightFactory {
/**
* 享元池
*/
private HashMap<String, Flyweight> flyweightMap = new HashMap<>();

/**
* 获取享元对象
*/
public Flyweight getFlyweight(String key) {
if (flyweightMap.containsKey(key)) {
return flyweightMap.get(key);
}
Flyweight flyweight = ···;
flyweightMap.put(key, flyweight);
return flyweight;
}
}

五、享元模式与 String

JDK 类库中的 String 类使用了享元模式。

六、优缺点

1. 优点

  • 可以减少内存占用,节省系统资源

2. 缺点

  • 使系统变得更复杂
  • 为实现相似对象的复用,需要抽象内部状态和外部状态

参考

  • 《Java 设计模式》