享元模式
# 一、概述
享元(Flyweight)模式摒弃了在每个对象中保存所有数据的方式,通过共享多个对象共有的相同状态,使得能在有限的内存中加载更多对象。
享元对象的核心是系统中会产生大量的同类对象,通过共享对象内在状态,不需要总是创建新对象,从而降低内存占用并提高效率。正因为这样,所以享元模式在池化技术中得到了广泛应用。
# 1.1 解决了什么问题
在坦克大战游戏中会产生大量的子弹,每个子弹都可以被认为是一个对象。和子弹类似的还有坦克对象、墙体对象等等,在一局十几分钟的游戏中,可能会产生许许多多的子弹对象、坦克对象、墙体对象等。如果每个对象都是被独立 new 出来的,那么每个子弹对象、坦克对象、墙体对象都需要加载自己对应的矢量图以及其它一些属性信息,这样会造成很大的空间浪费,甚至对于一些配置较低的机器会造成卡顿,严重影响游戏体验。
实际上有些对象的属性是可以重复利用的,而不需要每次都重新加载。就拿子弹对象来说,同一类型的子弹(相同型号坦克发射的子弹)的矢量图、子弹大小、颜色、杀伤力、移动速度都是一样的,如果对这些相同的属性只创建一个实例对象重复利用,是不是可以节省很多内存呢?
# 1.2 解决方案
对象中的常量属性通常被称为内在状态,因为这些属性在对象外部是不能改变的,而那些允许在对象外部改变的属性被称为外在状态。
在坦克大战这个案例中,子弹的矢量图、子弹大小、颜色、杀伤力可以被认为是内在状态,而子弹的方向、和初始化的坐标,可以认为是外在状态。如果能够将对象的内在状态抽象出来统一存储进行共享,那么外在状态在需要的时候及时创建就可以满足需求,将这种只存储内在状态的对象称为享元。
在享元模式中,内在状态各个对象之间共享,外部状态由客户端传入。
# 二、实现方式
# 2.1 角色
- Flyweight:享元接口,定义所有对象的共享操作。
- ConcreteFlyweight:具体的享元角色,实现享元接口,是被共享的部分,其属性为内在状态。
- UnSharedConcreteFlyweight(非必须):同样实现享元接口,当不需要共享对象时,但又需要以统一的接口处理此对象,可以添加此角色。
- FlyweightFactory:享元工厂类,构建一个共享容器池和从池中获取对象的方法,为客户端提供共享对象。
# 2.2 代码
定义 Flyweight 接口:
public interface Bullet {
void draw(int x, int y);
}
2
3
定义享元角色 ConcreteFlyweight,该对象内部的属性均为内在状态,是共享的部分:
import java.awt.*;
public class GeneralBullet implements Bullet {
private final String name = "普通炮弹";
// 为了简化,矢量图用字符串替代
private final String img = "xxx";
private final int size = 50;
private final Color color = Color.BLACK;
private final int lethality = 100;
@Override
public void draw(int x, int y) {
System.out.println(String.format("普通炮弹位置:%d, %d", x, y));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.awt.*;
public class SuperBullet implements Bullet {
private final String name = "超级炮弹";
// 为了简化,矢量图用字符串替代
private final String img = "yyy";
private final int size = 100;
private final Color color = Color.YELLOW;
private final int lethality = 200;
@Override
public void draw(int x, int y) {
System.out.println(String.format("超级炮弹位置:%d, %d", x, y));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
定义 FlyweightFactory,达到共享的效果:
public class BulletFactory {
private static final Map<String, Bullet> map = new HashMap<>();
public static Bullet getBullet(String name) {
Bullet bullet = map.get(name);
if (bullet == null) {
if (name.equals("普通炮弹")) {
bullet = new GeneralBullet();
} else if (name.equals("超级炮弹")) {
bullet = new SuperBullet();
} else {
throw new IllegalArgumentException();
}
map.put(name, bullet);
}
return bullet;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
客户端:
public class FlyweightTest {
public static void main(String[] args) {
Set<Integer> generalSet1 = new HashSet<>();
Set<Integer> generalSet2 = new HashSet<>();
// 通过传统的 new 方式创建10000个对象
for (int i = 0; i < 10000; i++) {
GeneralBullet bullet = new GeneralBullet();
bullet.draw(i, i + 1);
generalSet1.add(bullet.hashCode());
}
// 通过 FlyweightFactory 获取10000个对象
for (int i = 0; i < 10000; i++) {
Bullet bullet = BulletFactory.getBullet("普通炮弹");
bullet.draw(i, i + 1);
generalSet2.add(bullet.hashCode());
}
System.out.println(generalSet1.size());
System.out.println(generalSet2.size());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
......
10000
1
2
3
# 三、源码中的应用
- java.lang.Integer#valueOf(int)
- java.lang.Boolean#valueOf(boolean)
- java.lang.Byte#valueOf(byte)
- java.lang.Character#valueOf(char)