享元模式采用共享机制来避免大量拥有相同内容对象的开销。这种开销最常见、最直观的就是内存的损耗。享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)。
- 内蕴状态是存储在享元对象内部的,并且是不会随环境的改变而有所不同。因此,一个享元可以具有内蕴状态并可以共享。
- 外蕴状态是随环境的改变而改变的、不可以共享的。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态不可以影响享元对象的内蕴状态,它们是相互独立的。
组成结构
享元模式一般有三个角色:
- 抽象享元(Flyweight)角色 :给出一个抽象接口,以规定出所有具体享元角色需要实现的方法。
- 具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定出的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。
- 享元工厂(FlyweightFactory)角色 :本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
简单应用
以象棋游戏为例,假设我们把每颗棋子看成是一个对象,那么每开启一个棋局需要创建32个棋子对象,那如果同时存在一百万个棋局的话就很可怕了!!!我们试着用享元模式解决这个问题。
首先划分外蕴状态和内蕴状态
外蕴状态:不同的棋子角色是不确定的,创建棋子的时候才会确认是什么角色。
内蕴状态:棋子的形状和大小基本是不会变化的,不会随着棋子角色变化而变化。
1、抽象享元(Flyweight)角色
定义一个创建棋子的接口
public interface IChess {
/**
* 棋子信息
*/
void info();
}
2、具体享元(ConcreteFlyweight)角色
实现棋子的创建并打印棋子信息
public class Chess implements IChess {
public static final String TAG = "Chess";
//可变
private String role; //棋子角色
//不可变
private String shape = "CIRCLE"; //棋子形状
private int radius = 100; //棋子半径大小
public Chess(String role) {
this.role = role;
}
@Override
public void info() {
Log.d(TAG, String.format("角色%s,形状%s,大小%d", role, shape, radius));
}
}
3、享元工厂(FlyweightFactory)角色
负责创建棋子,使用HashMap保存已创建的棋子达到复用目的
public class ChessFactory {
private static HashMap<String, Chess> chessHashMap = new HashMap<>(); //负责存储共享对象
//如果共享Map内已经存在role角色的棋子,直接复用;否则创建新棋子
public static Chess getChess(String role) {
Chess chess = chessHashMap.get(role);
if (chess == null) {
Log.d(TAG, "=================创建一个新的棋子=================");
chess = new Chess(role);
chessHashMap.put(role, chess);
}
return chess;
}
}
随机创建30个棋子(六种角色),查看程序运行结果:
public void button(View view) {
String[] roles = {"将", "帅", "车", "马", "炮", "兵"};
//创建30个棋子
for (int i = 0; i < 30; i++) {
ChessFactory.getChess(roles[(int) (Math.random() * 1000) % 6]).createChess();
}
}
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: =================创建一个新的棋子=================
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: 角色车,形状CIRCLE,大小100
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: =================创建一个新的棋子=================
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: 角色炮,形状CIRCLE,大小100
2020-08-11 15:20:56.774 16675-16675/com.android.multidex D/Chess: =================创建一个新的棋子=================
2020-08-11 15:20:56.775 16675-16675/com.android.multidex D/Chess: 角色马,形状CIRCLE,大小100
2020-08-11 15:20:56.775 16675-16675/com.android.multidex D/Chess: =================创建一个新的棋子=================
2020-08-11 15:20:56.775 16675-16675/com.android.multidex D/Chess: 角色兵,形状CIRCLE,大小100
2020-08-11 15:20:56.775 16675-16675/com.android.multidex D/Chess: 角色炮,形状CIRCLE,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: 角色马,形状CIRCLE,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: =================创建一个新的棋子=================
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: 角色将,形状CIRCLE,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: 角色兵,形状CIRCLE,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: 角色将,形状CIRCLE,大小100
2020-08-11 15:20:56.776 16675-16675/com.android.multidex D/Chess: =================创建一个新的棋子=================
2020-08-11 15:20:56.777 16675-16675/com.android.multidex D/Chess: 角色帅,形状CIRCLE,大小100
2020-08-11 15:20:56.777 16675-16675/com.android.multidex D/Chess: 角色帅,形状CIRCLE,大小100
2020-08-11 15:20:56.777 16675-16675/com.android.multidex D/Chess: 角色车,形状CIRCLE,大小100
2020-08-11 15:20:56.777 16675-16675/com.android.multidex D/Chess: 角色帅,形状CIRCLE,大小100
2020-08-11 15:20:56.778 16675-16675/com.android.multidex D/Chess: 角色车,形状CIRCLE,大小100
2020-08-11 15:20:56.778 16675-16675/com.android.multidex D/Chess: 角色将,形状CIRCLE,大小100
2020-08-11 15:20:56.778 16675-16675/com.android.multidex D/Chess: 角色马,形状CIRCLE,大小100
2020-08-11 15:20:56.778 16675-16675/com.android.multidex D/Chess: 角色马,形状CIRCLE,大小100
2020-08-11 15:20:56.779 16675-16675/com.android.multidex D/Chess: 角色帅,形状CIRCLE,大小100
2020-08-11 15:20:56.779 16675-16675/com.android.multidex D/Chess: 角色帅,形状CIRCLE,大小100
2020-08-11 15:20:56.779 16675-16675/com.android.multidex D/Chess: 角色兵,形状CIRCLE,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色马,形状CIRCLE,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色马,形状CIRCLE,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色炮,形状CIRCLE,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色将,形状CIRCLE,大小100
2020-08-11 15:20:56.780 16675-16675/com.android.multidex D/Chess: 角色将,形状CIRCLE,大小100
2020-08-11 15:20:56.781 16675-16675/com.android.multidex D/Chess: 角色马,形状CIRCLE,大小100
2020-08-11 15:20:56.781 16675-16675/com.android.multidex D/Chess: 角色马,形状CIRCLE,大小100
2020-08-11 15:20:56.781 16675-16675/com.android.multidex D/Chess: 角色车,形状CIRCLE,大小100
2020-08-11 15:20:56.781 16675-16675/com.android.multidex D/Chess: 角色马,形状CIRCLE,大小100
从结果可以看出,每个角色的棋子都只有在第一次使用的时候需要创建,也就是说棋子对象的个数只与角色数量有关。这样就成功实现了对象的共享复用,减少了因重复创建相同内容的对象带来的内存开销。
优缺点
优点
- 极大的减少系统中对象的个数,降低内存的消耗;
- 享元模式 的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点
为了使对象可以共享,需要划分内蕴状态和外蕴状态,使得程序的设计变得复杂。
享元模式与对象池的区别
相同点:
享元模式和对象池的最终目标是相同的,都是为了减少对象的数量,减少内存的使用。它们都是通过维护和共享一组对象实现对象的复用。
不同点:
享元模式是结构型模式。它把可以变化的状态剥离出来并对外提供接口,并且共享不变的东西。享元对外提供的接口常常会包含一个String类型的参数,通常参数与对象是一对一的关系。
而对象池是构造型模式,侧重于提供整个对象实例。对调用者而言对象池提供的对象都没有区别,这个可以用,那个也可以用。
网友评论