Java设计模式—享元模式

享元模式采用共享机制来避免大量拥有相同内容对象的开销。这种开销最常见、最直观的就是内存的损耗。享元对象能做到共享的关键是区分内蕴状态(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 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

从结果可以看出,每个角色的棋子都只有在第一次使用的时候需要创建,也就是说棋子对象的个数只与角色数量有关。这样就成功实现了对象的共享复用,减少了因重复创建相同内容的对象带来的内存开销。
优缺点
优点
  • 极大的减少系统中对象的个数,降低内存的消耗;
  • 享元模式 的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点 为了使对象可以共享,需要划分内蕴状态和外蕴状态,使得程序的设计变得复杂。
享元模式与对象池的区别
相同点: 享元模式和对象池的最终目标是相同的,都是为了减少对象的数量,减少内存的使用。它们都是通过维护和共享一组对象实现对象的复用。
不同点: 【Java设计模式—享元模式】享元模式是结构型模式。它把可以变化的状态剥离出来并对外提供接口,并且共享不变的东西。享元对外提供的接口常常会包含一个String类型的参数,通常参数与对象是一对一的关系。
而对象池是构造型模式,侧重于提供整个对象实例。对调用者而言对象池提供的对象都没有区别,这个可以用,那个也可以用。

    推荐阅读