享元模式

面向对象技术可很好地解决一些灵活性可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致运行代价过高,带来性能下降等问题。享元模式正是为解决这一类问题而诞生的。

定义

享元模式Flyweight Pattern又称为轻量级模式是对象池的一种实现,类似于线程池,线程池可以避免不停的创建和销毁多个对象,消耗性能,提供了减少对象数量从而改善应用所需的对象结构的方式。其宗旨是共享细粒度对象,将多个对同一对象的访问集中起来,不必为每个访问者创建一个单独的对象,以此来降低内存的消耗,属于结构型模式

享元模式把一个对象的状态分成内部状态外部状态内部状态即是不变的外部状态是变化的;然后通过共享不变的部分,达到减少对象数量并节约内存的目的,享元模式模式的本质是缓存共享对象,降低内存消耗

实现

享元模式三个参与角色:

  • 抽象享元角色(Flyweight):享元对象抽象基类或接口,同时定义出对象的外部状态内部状态的接口或实现
  • 具体享元角色(ConcreteFlyweight):实现抽象角色定义的业务,该角色的内部状态处理应该与环境无关,不能出现会有一个操作改变内部状态,同时修改了外部状态
  • 享元工厂(FlyweightFactory):负责管理享元对象池创建享元对象
抽象享元角色
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public abstract class Flyweight {
//内部状态
private String intrinsic;
//外部状态
protected final String extrinsic;
//要求享元角色必须接受外部状态
public Flyweight(String extrinsic) {
this.extrinsic = extrinsic;
}
//定义业务操作
public abstract void operate();
//内部状态的getter/setter
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
}
具体享元角色
1
2
3
4
5
6
7
8
9
10
public class ConcreteFlyweight extends Flyweight {
// 接受外部状态
public ConcreteFlyweight(String extrinsic) {
super(extrinsic);
}
// 根据外部状态进行逻辑处理
public void operate() {
// 业务逻辑
}
}
享元工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FlyweightFactory {
//定义一个池容器
private static HashMap<String, Flyweight> pool = new HashMap<>();

//享元工厂
public static Flyweight getFlyweight(String extrinsic) {
//需要返回的对象
Flyweight flyweight = null;
//在池中没有该对象
if (pool.containsKey(extrinsic)) {
return pool.get(extrinsic);
} else {
//根据外部状态创建享元对象
flyweight = new ConcreteFlyweight(extrinsic);
//放置到池中
pool.put(extrinsic, flyweight);
}
return flyweight;
}
}

优点

  • 减少对象的创建,降低内存中对象的数量,降低系统的内存占用,增强程序的性能
  • 减少内存之外的其他资源占用

缺点

  • 需要关注内、外部状态、关注线程安全问题
  • 提高了系统复杂性,使系统、程序的逻辑复杂化

应用

当系统中多处需要同一组信息时,可把这些信息封装到一个对象中,然后对该对象进行缓存,这样一个对象就可以提供给多处需要使用的地方,避免大量同一对象的多次创建,消耗大量内存空间。

享元模式其实就是工厂模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法生成对象的,只不过享元模式中为工厂方法增加了缓存这一功能。主要总结为以下应用场景:

  • 常常应用于系统底层的开发,以便解决系统的性能问题
  • 系统有大量相似对象
  • 需要缓冲池的场景

扩展

  • JVM中的常量池、Integer、Long等包装对象也用到了享元模式