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

从结果可以看出,每个角色的棋子都只有在第一次使用的时候需要创建,也就是说棋子对象的个数只与角色数量有关。这样就成功实现了对象的共享复用,减少了因重复创建相同内容的对象带来的内存开销。

优缺点

优点
  • 极大的减少系统中对象的个数,降低内存的消耗;
  • 享元模式 的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
缺点

为了使对象可以共享,需要划分内蕴状态和外蕴状态,使得程序的设计变得复杂。

享元模式与对象池的区别

相同点:

享元模式和对象池的最终目标是相同的,都是为了减少对象的数量,减少内存的使用。它们都是通过维护和共享一组对象实现对象的复用。

不同点:

享元模式是结构型模式。它把可以变化的状态剥离出来并对外提供接口,并且共享不变的东西。享元对外提供的接口常常会包含一个String类型的参数,通常参数与对象是一对一的关系。
而对象池是构造型模式,侧重于提供整个对象实例。对调用者而言对象池提供的对象都没有区别,这个可以用,那个也可以用。

你可能感兴趣的:(Java设计模式—享元模式)