设计模式基础篇-05-享元模式(Flyweight Pattern)

1. 引入

享元模式简单的说其实就是: 完成缓冲池的创建,来达到缓冲池对象里面复用的功能;

1.1 源码引入

下面代码执行后答案是什么?

public class FlyweightTest {
    public static void main(String[] args) {
        Integer a = Integer.valueOf(127);
        Integer b = new Integer(127);
        System.out.println(a == b);
        int c = 127;
        System.out.println(a == c);
        System.out.println(b == c);
    }
}

答案是 false true true

1.1.2 Integer、new Integer() 和 int 比较的面试题

基本概念的区分:
1、Integer 是 int 的包装类,int 则是 java 的一种基本数据类型
2、Integer 变量必须实例化后才能使用,而int变量不需要
3、Integer 实际是对象的引用,当new一个 Integer时,实际上是生成一个指针指向此对象;而 int 则是直接存储数据值
4、Integer的默认值是null,int的默认值是0

1.2 Integer,new Integer() 和 int 的比较解答

  • 两个 new Integer() 变量比较 ,永远是 false
    因为new生成的是两个对象,其内存地址不同
Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j);  //false

  • Integer变量 和 new Integer() 变量比较 ,永远为 false。
    因为 Integer变量 指向的是 java 常量池 中的对象,
    而 new Integer() 的变量指向 堆中 新建的对象,两者在内存中的地址不同。
Integer i = new Integer(100);
Integer j = 100;
System.out.print(i == j);  //false
  • 两个Integer 变量比较,如果两个变量的值在区间-128到127 之间,则比较结果为true, 如果两个变量的值不在此区间,则比较结果为 false 。
Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true

Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false

分析:
Integer i = 100 在编译时,会翻译成为 Integer i = Integer.valueOf(100),
而 java 对 Integer类型的 valueOf 的定义如下:

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

java对于-128到127之间的数,会进行缓存。
所以 Integer i = 127 时,会将127进行缓存,下次再写Integer j = 127时,就会直接从缓存中取,就不会new了。

  • int 变量 与 Integer、 new Integer() 比较时,只要两个的值是相等,则为true
    因为包装类Integer 和 基本数据类型int 比较时,java会自动拆包装为int ,然后进行比较,实际上就变为两个int变量的比较。
Integer i = new Integer(100); //自动拆箱为 int i=100; 此时,相当于两个int的比较
int j = 100System.out.print(i == j); //true

1.2 源码案列总结

Integer、Lang、Byte、String等都用到了享元模式

Integer、Lang、Byte中的valueOf()方法用到了享元模式,String常量池就是享元模式。

具体看一下valueOf()方法的源码实现:

//valueOf()方法的具体实现

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

//Integer类中的静态方法,

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

在上面的源代码我们可以看到,如果Integer.valueOf(int i)中传递的i在[-128,127]之间,
就会直接从IntegerCache.cache[i + (-IntegerCache.low)]中取一个出来,如果不在这个范围,
才会去创建一个新的Integer。

看完源码就会发现在valueOf这个方法中它会先判断传进去的值是否在IntegerCache中, 如果不在就创建新的对象,在就直接返回缓存池里的对象。 这个valueOf方法就用到享元模式。
它将-128到127的Integer对象先在缓存池里创建好,等我们需要的时候直接返回即可。
所以在-128到127中的数值我们用valueOf创建会比new更快。

2. 实际案列

假设开始设计五子棋的架构,在分析到棋子那一块的时候,就可以想到了享元模式。
因为棋子虽多,但是除了黑白之分,摆放位置之分,其他都一样,这就非常适用于享元模式;

2.1 第一步,定义一个共享对象通用的接口

棋子对象有一个绘制自己的通用操作

public interface Chess {

    //绘制棋子
    void draw(int x,int y);
}

2.2 第二步,实现需要共享的对象类

棋子对象分为黑白两类,所以此处我们将颜色设计为对象的内部状态来共享,而不是外部状态,所以就需要黑白两个对象类。如果你把颜色作为外部状态,那么只需要一个对象类即可。

黑棋

import java.awt.*;

//黑棋
public class BlackChess implements Chess {
    //内部状态,共享
    private final Color color = Color.BLACK;

    private final String sharp = "圆形";

    public Color getColor() {
        return color;
    }

    @Override
    public void draw(int x, int y) {
        System.out.println(String.format("%s%s棋子置于(%d,%d)处", sharp, color.getAlpha(), x, y));
    }
}

白棋

import java.awt.*;

//白棋
public class WhiteChess implements Chess {
    //内部状态,共享
    private final Color color = Color.WHITE;

    private final String sharp = "圆形";

    public Color getColor() {
        return color;
    }

    @Override
    public void draw(int x, int y) {
        System.out.println(String.format("%s%s棋子置于(%d,%d)处", sharp, color.getAlpha(), x, y));
    }
}

2.3 第三步,共享对象工厂

其负责提供共享对象,客户端不应该直接实例化棋子对象,而应该使用此工厂来获取。因为我们分了黑白两类对象,所以这里使用Color为key的map来存储共享对象。

import java.awt.*;
import java.util.HashMap;
import java.util.Map;

public class ChessFactory {
    private static final Map<Color, Chess> chessMap = new HashMap<>();

    public static Chess getChess(Color color) {
        Chess chess = chessMap.get(color);
        if (chess == null) {
            chess = color == Color.WHITE ? new WhiteChess() : new BlackChess();
            chessMap.put(color, chess);
        }
        return chess;
    }
}

2.4 测试

import java.awt.*;

public class FlyweightClient {

    public static void main(String[] args) {
        //下黑子
        Chess backChess1 = ChessFactory.getChess(Color.BLACK);
        backChess1.draw(2, 5);

        //下白子
        Chess whiteChess = ChessFactory.getChess(Color.WHITE);
        whiteChess.draw(3, 5);

        //下黑子
        Chess backChess2 = ChessFactory.getChess(Color.BLACK);
        backChess2.draw(2, 6);

        System.out.println(String.format("backChess1:%d | backChess2:%d | whiteChess:%d",
                backChess1.hashCode(), backChess2.hashCode(), whiteChess.hashCode()));
    }
    
}

输出结果:
在这里插入图片描述
从输出可见,backChess1 与 backChess2 是同一个对象,都是黑子,只是棋子的摆放位置不一样,而whiteChess是另一个不同的对象。所以不论棋盘上有多少颗棋子,程序中只会保持最多两个棋子对象,这就极大的节约了内存。

3. 总结

首先一定要区分出内部状态与外部状态,共享对象只持有内部状态,内部状态不可以从客户端设置,而外部状态必须从客户端设置。
合理设计共享对象分类,大部分情况下会设计成一组,而不是一个共享对象。
共享对象要求是不可变对象,从FlyWeightFactory获取到的对象都是一个原始的对象,而不是一个状态不确定的对象。

优点:
极大的降低了程序的内存占用

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alan0517

感谢您的鼓励与支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值