设计模式 | 享元模式

一、作用和场景

享元,即共享的单元,该模式的目的是为了,复用对象,节省内存,前提是复用的对象是不可变的对象

关于不可变对象:
书中的解释是:对象一旦被创建后,对象所有的状态及属性在生命周期内不会发生任何变化,也就是对象创建后不能进行任何修改,不能改变对象内的成员变量,包括基本数据类型的值,引用类型的变量指向的对象也要具有不可变性

public class Object{
  private int value;
  public Object(int value){
  this.value = value
}
public int getValue(){
 return value;
}

上面的Object就是一个不可变类,状态值无法再修改。

String的不可变性:

String s = "A"
s="a"

创建了几个对象,经典的面试题,其实就是不可变性,s是对象的引用,指向的内存地址的值并没有改变,s="a"只是又创建了一个对象,并把引用指向了改内存地址。

不可变的保证了共享时一处修改而影响到其他地方的使用。
当一个业务场景中存在大量的不可变对象时就可以考虑享元模式,或者相似的对象也可以抽离相同的部分封装成一个享元对象。

二、例子

棋子的列子:
在线的象棋游戏场景,每个房间都会有棋盘,都会存在相同的棋子对象,而这些棋子都有相同且不需要改变的属性,颜色、字,为了规避每个房间都创建三十个棋子对象就可以使用享元模式:

//抽离的享元棋子类
public class ChessPieceUnit{ 
private int id;
private String text;
private Color color;
public ChessPieceUnit(int id,String text,Color color){
  .......
}
public static enum Color{
......
}

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 id){
  return pieces.get(id);
}
//棋子类
public class ChessPiece{
private ChessPieceUnit chessPieceUnit;
private int positionX;
private int positionY;
public ChessPiece(ChessPieceUnit unit,int x,int y){
.....
}
//.....
}

//棋盘类
public class ClassBoard{
  private Map<Integer,ChessPiece> chessPiece= new HashMap<>();
  public ClassBoard(){
     init();
  }
  private void init(){
     //通过工厂方法获取享元对象
      chessPiece.put(1,new ChessPiece(ChessPieceUnitFactory.getChessPiece(1),0,0));
      chessPiece.put(2,new ChessPiece(ChessPieceUnitFactory.getChessPiece(2),0,1));
      //.......
  }
}

Java Integer中的应用
又是一个经典的面试题如下:

Integer i1= 56;
Integer i2= 56;
Integer i3= 129;
Integer i4= 129;
sout(i1==i2);//true
sout(i3==i4);//false

很无聊的一道面试题,主要是考察自动装箱和java的==号,每种基本数据类型都会有对应的包装器类型,这之间的转换叫做拆箱和装箱。java中对于对象而言 ==的作用是比较内存地址是否相同而不是值是否相同。
但i2 == i1为啥是true?
Integer i1= 56 此处其实执行了,Integer.valueOf(56);
源码如下

 public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

//IntegerCache 静态类

 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() {}
    }

可以看到这里有个判断不是每次都会创建一个新的integer对象,而low和high的值就是 -128到127,IntegerCache中有一个static final Integer cache[];的数组变量用于存储享元对象。
为啥只存 -128 到127?
因为太多了,这区间的整形值只有一个字节的大小。
所以上面的 i1==i2就是ture了。
其他的基本数据类型都有类似的操作,如Long,Short,Byte.

Java String中的应用
还是面试题:

String s1 = "s";
String s2 = "s";
String s3 = new String("s");
sout(s1 == s2); //true
sout(s3==s1);//false

上面已经提到String类型是不可变的,而String符合不可变性,jvm会单独的开辟存储字符串常量的区域即 字符串常量池,**而常量池中的String对象就是共享单元,为所有的地方提供使用,这也解释了为啥String要是不可变的才行。**也体现了享元模式的重要前提,不可变性。
s1、s2引用指向的都是常量池中的字符串常量,而s3则是新创建的String对象。

对比单例、缓存、对象池

1.单例一个类只有一个对象供全局使用,享元模式的类可以创建多个对象,每个对象被多处代码引用共享,一个限制对象个数,一个是为了对象复用节省内存。

2.一般的缓存目的是为了加快访问的效率,比如一些创建成本很大的对象,比如需要网络请求IO操作之类的过程才能生成,会提前加载保存到内存中以备使用的时候调用,这时的缓存从某个方面来说也是复用,但出发点是提高访问效率,比如数据库缓存,CPU缓存,LRU缓存。

3.池化的优点是集中管理池中的对象,减少频繁的创建和销毁长期使用的对象,提高复用性,节约资源消耗,规避频繁的分配和释放内存,避免内存抖动,所以从复用的角度看,池化是为了节省时间提高性能,而享元模式是为了单纯的对象的复用节省创建的空间,抛去空间的占用好像也不存在其他的什么高的创建成本。

总之,享元的关键点是:不可变的对象,单纯的节省对象占用空间。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值