美文网首页
Java设计模式—享元模式

Java设计模式—享元模式

作者: 怡红快绿 | 来源:发表于2020-08-11 16:03 被阅读0次

    享元模式采用共享机制来避免大量拥有相同内容对象的开销。这种开销最常见、最直观的就是内存的损耗。享元对象能做到共享的关键是区分内蕴状态(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类型的参数,通常参数与对象是一对一的关系。
    而对象池是构造型模式,侧重于提供整个对象实例。对调用者而言对象池提供的对象都没有区别,这个可以用,那个也可以用。

    相关文章

      网友评论

          本文标题:Java设计模式—享元模式

          本文链接:https://www.haomeiwen.com/subject/libedktx.html