设计模式:享元模式

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

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

/**
 * @Author: zhangpeng
 * @Description:
 * @Date: 2022/8/22
 */
public class Poke{
    private Int pokerSuit;
    private String pokerNumber;
 
 public static enum pokerSuit{    RED, BLACK  }
   
}

那一个房间就要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;
    }
}

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

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<Integer, ChessPiece> 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) {
        //...省略...
        }

}

棋子的享元:

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方法...
}

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

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<Integer, ChessPieceUnit> 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);
        }
}

大家具体代码可以看我的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; //自动拆箱

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


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

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


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

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


Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2);
System.out.println(i3 == i4);

结果是这样的:
在这里插入图片描述
有没有感觉到很奇怪的,按理来说应该是要不都是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=<size>} 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() {}
    }

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);

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值