• 设计模式:享元模式


    这个设计模式享元模式,听名字的意思就是共享的单元,但是他的英文名称挺有意思的叫做“flyweight”,蝇量级拳手这个也挺有意思的,实际就是做一个共享的模块,里面是我们常用的,而且可以帮助我们减少创建对象,更好的节省资源,把重量级的活缩减成蝇量级的活。

    一 初步理解享元
    具体的例子,其实就是线上棋牌游戏,假设有很多个房间,每个房间都要有一副牌,牌的数量,花色,大小也就那些,假设我们玩的是一副扑克牌,一副扑克牌应该是54张,咱们完玩的是斗地主。每个牌对应的是一下:

    /**
     * @Author: zhangpeng
     * @Description:
     * @Date: 2022/8/22
     */
    public class Poke{
        private Int pokerSuit;
        private String pokerNumber;
     
     public static enum pokerSuit{    RED, BLACK  }
       
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    那一个房间就要new出54个对象,一万个房间就是540000,但是明显太占空间了,尤其是线上这种业务很容易内存爆了,影响业务影响体验。
    那我们可不可以共享这么一副牌,类似一个枚举,大家都是这一副牌,我只需要记录手牌是什么,以及扔掉的牌的记录就好了。

    二 一个简单的棋类享元demo

    同理玩牌是这样,棋类游戏也是类似。我们这里有一个demo是关于棋的

    注释的部分是改造前的

    package com.example.test.flyweight.chess;
    
    /**
     * @Author: zhangpeng
     * @Description:  享元模式---棋子
     * @Date: 2022/8/22
     */
    public class ChessPiece {
        //常规模式下的棋子代码
    //    private int id;
    //    private String text;
    //    private Color color;
    //    private int positionX;
    //    private int positionY;
    //
    //    public ChessPiece(int id, String text, Color color, int positionX, int positionY) {
    //        this.id = id;
    //        this.text = text;
    //        this.color = color;
    //        this.positionX = positionX;
    //        this.positionY = positionX;
    //    }
    //
    //    public static enum Color {    RED, BLACK  }
    //
    //    // ...省略其他属性和getter/setter方法...}
    
    
    
        private ChessPieceUnit chessPieceUnit;
        private int positionX;
        private int positionY;
        public ChessPiece(ChessPieceUnit unit, int positionX, int positionY) {
            this.chessPieceUnit = unit;
            this.positionX = positionX;
            this.positionY = positionY;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    棋盘这也要对比改造前后的

    package com.example.test.flyweight.chess;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @Author: zhangpeng
     * @Description:  享元模式---棋局
     * @Date: 2022/8/22
     */
    public class ChessBoard {
    //    常规模式下的棋盘
    //    private Map chessPieces = new HashMap<>();
    //    public ChessBoard() {    init();  }
    //    private void init() {
    //        chessPieces.put(1, new ChessPiece(1, "車", ChessPiece.Color.BLACK, 0, 0));
    //        chessPieces.put(2, new ChessPiece(2,"馬", ChessPiece.Color.BLACK, 0, 1));    //...省略摆放其他棋子的代码...
    //        }
    //        public void move(int chessPieceId, int toPositionX, int toPositionY) {
    //        //...省略...
    //        }
    
    
    
        private Map chessPieces = new HashMap<>();
        public ChessBoard() { init(); }
        private void init() {
            chessPieces.put(1, new ChessPiece( ChessPieceUnitFactory.getChessPiece(1), 0,0));
            chessPieces.put(1, new ChessPiece( ChessPieceUnitFactory.getChessPiece(2), 1,0)); //...省略摆放其他棋子的代码...
            }
            public void move(int chessPieceId, int toPositionX, int toPositionY) {
            //...省略...
            }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    棋子的享元:

    package com.example.test.flyweight.chess;
    
    /**
     * @Author: zhangpeng
     * @Description:
     * @Date: 2022/8/22
     */
    public class ChessPieceUnit {
        private int id;
        private String text;
        private Color color;
        public ChessPieceUnit(int id, String text, Color color) {
            this.id = id; this.text = text; this.color = color;
        }
        public static enum Color { RED, BLACK } // ...省略其他属性和getter方法...
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    享元的工厂,这里缓存要用到的所有棋子

    package com.example.test.flyweight.chess;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @Author: zhangpeng
     * @Description:
     * @Date: 2022/8/22
     */
    public class ChessPieceUnitFactory {
    
        private static final Map pieces = new HashMap<>();
        static {
            pieces.put(1, new ChessPieceUnit(1, "車", ChessPieceUnit.Color.BLACK));
            pieces.put(2, new ChessPieceUnit(2,"馬", ChessPieceUnit.Color.BLACK));
            //...省略摆放其他棋子的代码...
            }
            public static ChessPieceUnit getChessPiece(int chessPieceId)
            {
                return pieces.get(chessPieceId);
            }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    大家具体代码可以看我的git提交,除了棋子的享元的damo还有一个是关于简单文本对于字体格式的享元demo(大家可以star一下这个库,基本我的设计模式demo和别的一些工具demo我都会放在这里)
    享元demo

    三 具体解析享元demo
    享元重要的是一个共享的那部分是不能改变的。
    他的使用场景就是当程序中存在大量相似对象,每个对象之间只是根据不同的使用场景有些许变化时,考虑对他们提取出来改造,节省空间。类似于有一个工厂来单独管理他们。

    像我们网盘有的链接保存也是这样,你复制一个文本的链接像保存你的网盘里,文件很大,但是人家网盘几乎毫秒级别的相应,实际上人家没有做什么复制保存这些消耗IO的操作,人家就是把文件对应的地址保存一下,实际上这个地址你可以理解为一个享元。

    还有我们demo当中所谓的一些缓存也是这么做,我直接缓存一些你常用的,等你用的时候,感觉“好快”。

    借用一个别人的图说明一下享元模式的结构
    在这里插入图片描述
    Flyweight
    享元接口,定义所有对象共享的操作

    ConcreteFlyweight
    具体的要被共享的对象,其一般是一个不可变类,内部只保存需要共享的内部状态,它可能不止一个。

    FlyweightFactory
    负责给客户端提供共享对象

    以上的三部分对应我们的demo

    下棋的这一步应该是享元接口单手i我这边没有体现,共享的对象ChessPieceUnit ,ChessPieceUnitFactory 提供共享对象的工厂

    四 常用的缓存
    大多数情况下,享元模式的代码实现非常简单,主要是通过工厂模式,在工厂类中,通过一个 Map 或者 List 来缓存已经创建好的享元对象,以达到复用的目的。

    我们常见的享元模式可以从基本类型包装类种体会.
    首先要明白一个自动拆箱,自动装箱的概念。很简单以下代码就是:

    
    Integer i = 56; //自动装箱
    int j = i; //自动拆箱
    
    • 1
    • 2
    • 3

    装箱就是一个基本类型直接转换成包装类,比如Integer i = 56。
    但实际“自动”替你做了一步

    
    #Integer i = 59;底层执行了:但是有没有展示出来
    Integer i = Integer.valueOf(59);
    
    • 1
    • 2
    • 3

    同理拆箱,就是一个包装类转换成它对应的基本类型

    
    #int j = i; 底层执行了:i是包装类的实例
    int j = i.intValue();
    
    • 1
    • 2
    • 3

    了解上面这些,我们可以执行下面代码玩玩,但是在这之前可以先预估一下结果:

    
    Integer i1 = 56;
    Integer i2 = 56;
    Integer i3 = 129;
    Integer i4 = 129;
    System.out.println(i1 == i2);
    System.out.println(i3 == i4);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    结果是这样的:
    在这里插入图片描述
    有没有感觉到很奇怪的,按理来说应该是要不都是false,要不都是true怎么同样的操作,结果不一样,怎么JDK不注意“幂等性”吗?

    不是的实际上,这个是包装类的优化,所谓的优化就是Integer类做了一个缓存。
    在代码里面就是内部类IntegerCache

    在这里插入图片描述

        /**
         * Cache to support the object identity semantics of autoboxing for values between
         * -128 and 127 (inclusive) as required by JLS.
         *
         * The cache is initialized on first usage.  The size of the cache
         * may be controlled by the {@code -XX:AutoBoxCacheMax=} option.
         * During VM initialization, java.lang.Integer.IntegerCache.high property
         * may be set and saved in the private system properties in the
         * sun.misc.VM class.
         */
    
        private static class IntegerCache {
            static final int low = -128;
            static final int high;
            static final Integer cache[];
    
            static {
                // high value may be configured by property
                int h = 127;
                String integerCacheHighPropValue =
                    sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
                if (integerCacheHighPropValue != null) {
                    try {
                        int i = parseInt(integerCacheHighPropValue);
                        i = Math.max(i, 127);
                        // Maximum array size is Integer.MAX_VALUE
                        h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                    } catch( NumberFormatException nfe) {
                        // If the property cannot be parsed into an int, ignore it.
                    }
                }
                high = h;
    
                cache = new Integer[(high - low) + 1];
                int j = low;
                for(int k = 0; k < cache.length; k++)
                    cache[k] = new Integer(j++);
    
                // range [-128, 127] must be interned (JLS7 5.1.7)
                assert IntegerCache.high >= 127;
            }
    
            private IntegerCache() {}
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    IntegerCache 只缓存 -128 到 127 之间的整型值,为什么不多弄点,要考虑你缓存太多也占空间,不如就一个字节大小,也就是2的7到-2的7次方-1(不太会打符号,将就看吧,理解就行)

    同理别的数字类型的基础类也会有类似缓存。
    String也有类似的样子,请看以下代码:

    
            String str1 = "ZP";
            String str2 = "ZP";
            String str3 = new String("zp");
    
            System.out.println(str1 == str2);
            System.out.println(str1 == str3);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述
    至于解释,我直接抄小争哥的对应设计模式的图就可以
    在这里插入图片描述
    这么一看,实际享元模式在我们日常的代码源码中使用的比较广泛,包括我们自己写框架,写到涉及到缓存的部分基本都会有这个模式,但是这个也要跟创建模型的单例区别开,虽然都是不可变的,但是使用场景和目的不一样,享元模式是结构型的设计模式。

  • 相关阅读:
    【考研数学】八.曲面积分
    C++11 decltype 的简单使用
    Spring(六)- BeanFactoryPostProcessor 与 BeanPostProcessor 后处理器
    9.1 链表
    线性表——顺序表和链表
    2022 CocoaPods安装教程
    Linux 作业
    TypeScript学习文档-基础篇(完结)
    JAVA经典百题之3的倍数
    【JavaScript】深入浅出理解事件循环
  • 原文地址:https://blog.csdn.net/FeiChangWuRao/article/details/126465482