享元设计模式

享元模式

“享元”就是被共享的单元。享元模式的使用意图就是复用对象,节省内存,应用的前提是被共享的对象是不可变对象。

具体来说,就是当一个系统中存在大量的重复对象时,如果这些重复的对象时不可变对象,我们就可以利用享元模式,将对象设计成享元,在内存中只保留一份实例,供多处代码引用。这样就可以减少内存中对象的数量,起到节省内存的目的。实际上我们还能抽取相似对象,我们也可以将这些对象中相同的部分提取出来设计成享元,让这些相似的对象去引用享元。

享元模式的简单举例

假设我们现在要开发一个棋牌游戏,比如象棋。在一个游戏大厅有成千上万的虚拟棋局,每个房间对应一个棋盘。每个棋盘要保存每个棋子的信息。利用这些信息就能给用户提供一个完整的棋盘。大致的实现如下:

//棋子
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 positonY){
        this.id = id;
        this.text = text;
        this.color = color;
        this.positionX = positionX;
        this.positionY = positionY;
    }
    
    public static enum Color{
        RED,BLACK
    }
    //....省略其他的属性和方法,以及getter和setter方法.....
}


//棋局
public class ChessBoard{
    private Map<Interger , ChessPiece> chessPieces = new HashMap<>();
    public ChessBoard(){
        init();
    }
    private void init(){
        chessPiece.put(1,new ChessPiece(1,"车",ChessPiece.Color.BLACK,0,0));
        //.......省略摆放其他棋子的代码...........
    }
    private void move(){
        //..........省略这部分代码............
    }
}

为了记录每个房间当前的棋盘情况,我们需要给每个房间都创建一个ChessBoard类的对象。因为游戏大厅中有成千上万个虚拟的房间,所以保存这么多的对象会消耗大量的内存。这个时候享元模式就派上用场了,对于上述的实现方式,内存会存在大量的相似对象,这些相似的对象的id,text,color都是相同的,只有positionX,positionY是不同的,因此可以将相同的属性抽离出来,成为一个享元,供其他的棋局复用。这样每个棋盘只需要记录每个棋子的位置信息。重构之后的代码如下:

//享元类
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方法.....
}
//工厂类缓存所有的棋子
public class ChessPieceUnitFactory{
    private static final Map<Integer , ChessPieceUnit> pieces = new HashMap<>();
    static{
        pieces.put(1,new ChessPieceUnit(1,"车",ChessPieceUnit.Color.BLACK));
        //.....省略其他的棋子的代码;
    }
    
    public static ChessPieceUnit getChessPiece(int chessPieceId){
        return pieces.get(chessPieceId);
    }
}


//棋子类
public class ChessPiece{
    private ChessPieceUnit chessPieceUnit;
    private int positionX;
    private int positionY;
    public ChessPiece(ChessPiece unit , int positionX , int positionY){
        this.chessPieceUnit = unit;
        this.positionX = positionX;
        this.positionY = positionY;
    } 
    //......省略getter,setter方法.......
}

//棋局类
public class ChessBoard{
    private Map<Integer , chessPiece> chessPieces = new HashMap<>();
    
    public ChessBoard(){
        init();
    }
    private void init(){
        chessPiece.put(1,new ChessPiece(ChessPieceUnitFactory.getChessPieceUnit(1)),0,0);
        //....省略其他棋子的代码.....
    }
    private void move(){
        //.....省略代码实现......
    }
}

在上面的代码中,我们利用工厂类ChessPieceUnitFactory来缓存ChessPieceUnit享元类的对象。所有的ChessBord类的对象共享这32个ChessPieceUnit享元类对象。如果我们不使用享元模式的话,那么1万个棋局要创建32万个ChessPiece棋子类的对象,但是使用了享元模式,只需要创建32个ChessPieceUnit享元类对象,供所有棋局共享使用,很大程度上节省了内存。

实际上,享元模式对JVM的”垃圾“回收并不友好。因为工厂类一直保存了对享元类的对象的引用,所以,这就导致享元类的对象在没有任何代码使用的情况下,也不会被JVM的"垃圾"回收机制自动回收。在某些情况下,如果对象的生命周期很短,也不会被密集使用,那么利用享元模式反而可能浪费更多的内存。因此,除非通过验证,利用享元模式可以大大的节省内存,否者,就不要过度的使用这个模式。为了这一点点的内存节省而引入一个复杂的设计模式,得不偿失。

享元模式在Java Integer中的使用

在了解享元模式在Integer的使用之前,我们先来理解一下什么是自动装箱(autoboxing)和自动拆箱(unboxing)?以及,Java中是如何判断两个Java对象是否相等(也就是代码中的"=="操作符的含义)?

在Java中,为基本类型提供了对应的包装器类型,如下表

基本数据类型对应的包装器类型
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean
shortShort
byteByte
charCharacter

自动装箱(autoboxing)是指自动将基本数据类型转化为包装器类型。

自动拆箱(unboxing)是指自动将包装器类型转换为基本数据类型。

例如:

Integer i = 56; //自动装箱
int j = i; //自动拆箱

数值56是基本数据类型(int类型),当赋值给包装类型(Integer类型)的变量时,就会触发自动装箱操作,创建一个Integer对,并且赋值给变量i。实际上“Integer i = 56”这条语句执行了“Integer i = Integer.valueOf(59)”语句。反过来,把包装器类型的变量i赋值给基本数据类型变量j时,就会执行自动拆箱操作,将i中的数据取出来,并赋值给就。“int j = i”这条语句底层执行了"int j = i.intValue()"这条语句。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值