第五章 5.7 享元模式

分类:01_设计模式

标签:

5.7.1 享元模式介绍

享元模式 (flyweight pattern) 的原始定义是:摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,从而让我们能在有限的内存容量中载入更多对象。

从这个定义中你可以发现,享元模式要解决的核心问题就是节约内存空间,使用的办法是找出相似对象之间的共有特征,然后复用这些特征。所谓“享元”,顾名思义就是被共享的单元。

比如: 一个文本字符串中存在很多重复的字符,如果每一个字符都用一个单独的对象来表示,将会占用较多的内存空间,我们可以使用享元模式解决这一类问题.

101.jpg

享元模式通过共享技术实现相同或者相似对象的重用,在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上他们却是共享同一个享元对象.

5.7.2 享元模式原理

享元模式的结构较为复杂,通常会结合工厂模式一起使用,在它的结构图中包含了一个享元工厂类.

102.jpg

享元模式的主要有以下角色:

5.7.3 享元模式实现


/**
* 抽象享元类
* @author spikeCong
* @date 2022/10/10
**/
public abstract class Flyweight {

   public abstract void operation(String extrinsicState);

}

/**
* 可共享-具体享元类
* @author spikeCong
* @date 2022/10/10
**/
public class ConcreteFlyweight extends Flyweight {

   //内部状态 intrinsicState作为成员变量,同一个享元对象的内部状态是一致的
   private String intrinsicState;

   public ConcreteFlyweight(String intrinsicState) {
       this.intrinsicState = intrinsicState;
   }

   /**
    * 外部状态在使用时由外部设置,不保存在享元对象中,即使是同一个对象
    * @param extrinsicState  外部状态,每次调用可以传入不同的外部状态
    */
   @Override
   public void operation(String extrinsicState) {
       //实现业务方法
       System.out.println("=== 享元对象内部状态" + intrinsicState +",外部状态:" + extrinsicState);
   }
}

/**
* 非共享具体享元类
* @author spikeCong
* @date 2022/10/10
**/
public class UnsharedConcreteFlyweight extends Flyweight {

   private String intrinsicState;

   public UnsharedConcreteFlyweight(String intrinsicState) {
       this.intrinsicState = intrinsicState;
   }


   @Override
   public void operation(String extrinsicState) {
       System.out.println("=== 使用不共享对象,内部状态: " + intrinsicState +",外部状态: " + extrinsicState);
   }
}

/**
* 享元工厂类
*      作用: 作为存储享元对象的享元池.用户获取享元对象时先从享元池获取,有则返回,没有创建新的
*      享元对象返回给用户,并在享元池中保存新增的对象.
* @author spikeCong
* @date 2022/10/10
**/
public class FlyweightFactory {

   //定义一个HashMap用于存储享元对象,实现享元池
   private Map<String,Flyweight> pool = new HashMap();

   public FlyweightFactory() {
       //添加对应的内部状态
       pool.put("A",new ConcreteFlyweight("A"));
       pool.put("B",new ConcreteFlyweight("B"));
       pool.put("C",new ConcreteFlyweight("C"));
   }

   //根据内部状态来进行查找
   public Flyweight getFlyweight(String key){
       //对象存在,从享元池直接返回
       if(pool.containsKey(key)){
           System.out.println("===享元池中存在,直接复用,key:" + key);
           return pool.get(key);

       }else{
           //如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
           System.out.println("===享元池中不存在,创建并复用,key:" + key);
           Flyweight fw = new ConcreteFlyweight(key);
           pool.put(key,fw);
           return fw;
       }
   }
}

5.7.4 享元模式应用实例

五子棋中有大量的黑子和白子,它们的形状大小都是一样的,只是出现的位置不同,所以一个棋子作为一个独立的对象存储在内存中,会导致大量的内存的浪费,我们使用享元模式来进行优化.

103.jpg

类图如下

104.jpg

代码如下


/**
* 抽象享元类: 五子棋类
* @author spikeCong
* @date 2022/10/10
**/
public abstract class GobangFlyweight {

   public abstract String getColor();

   public void display(){
       System.out.println("棋子颜色: " + this.getColor());
   }
}

/**
* 共享享元类-白色棋子
* @author spikeCong
* @date 2022/10/10
**/
public class WhiteGobang extends GobangFlyweight{

   @Override
   public String getColor() {
       return "白色";
   }
}

/**
* 共享享元类-黑色棋子
* @author spikeCong
* @date 2022/10/10
**/
public class BlackGobang extends GobangFlyweight {

   @Override
   public String getColor() {
       return "黑色";
   }
}

/**
* 享元工厂类-生产围棋棋子,使用单例模式进行设计
* @author spikeCong
* @date 2022/10/10
**/
public class GobangFactory {

   private static GobangFactory factory = new GobangFactory();

   private static Map<String,GobangFlyweight> pool;

   //设置共享对象的内部状态,在享元对象中传递
   private GobangFactory() {
       pool = new HashMap<String,GobangFlyweight>();
       GobangFlyweight black = new BlackGobang(); //黑子
       GobangFlyweight white = new WhiteGobang(); //白子
       pool.put("b",black);
       pool.put("w",white);
   }

   //返回享元工厂类唯一实例
   public static final GobangFactory getInstance(){
       return SingletonHolder.INSTANCE;
   }

   //静态内部类-单例
   private static class SingletonHolder{
       private static final GobangFactory INSTANCE = new GobangFactory();
   }

   //通过key获取集合中的享元对象
   public GobangFlyweight getGobang(String key){
       return pool.get(key);
   }
}

public class Client {

   public static void main(String[] args) {

       //获取享元工厂对象
       GobangFactory instance = GobangFactory.getInstance();

       //获取3颗黑子
       GobangFlyweight b1 = instance.getGobang("b");
       GobangFlyweight b2 = instance.getGobang("b");
       GobangFlyweight b3 = instance.getGobang("b");
       System.out.println("判断两颗黑子是否相同: " + (b1 == b2));

       //获取2颗白子
       GobangFlyweight w1 = instance.getGobang("w");
       GobangFlyweight w2 = instance.getGobang("w");
       System.out.println("判断两颗白子是否相同: " + (w1 == w2));

       //显示棋子
       b1.display();
       b2.display();
       b3.display();
       w1.display();
       w2.display();
   }
}

三颗黑子(两颗白子)对象比较之后内存地址都是一样的.说明它们是同一个对象.在实现享元模式时使用了单例模式和简单工厂模式,保证了享元工厂对象的唯一性,并提供工厂方法向客户端返回享元对象.

5.7.5 享元模式总结

1) 享元模式的优点

2) 享元模式的缺点

3) 使用场景


修改内容