重学设计模式(三、设计模式-享元模式)

93 阅读5分钟

1、享元模式

​ 首先我们思考一个问题?一个属性被赋值后不能被修改就称这个属性不可变,一个对象在被创建后,它的状态不可被改变就称这个对象不可变。

​ 为什么java的设计者将String对象设置为不可变类?

​ String类在java中被大量的运用,String被设计成不可变的主要目的还是为了安全和高效

​ 其安全体现在:String类被设置为final类,不能被继承,也其内部结构是稳定的;String对象的值value是个 private final char value[]被final修饰的私有数组,没有暴露任何设置此value的方法。不会被调用者不经意间的修改而影响到其他人。

​ 其高效体现在:字符串在常量池的共享可以节省空间,提升效率,如果复制一个字符串变量,原始字符串与复制字符串共享相同的字符,不会为其开辟新的内存空间。

​ 在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象的问题,如果能通过共享已经存在的对象来大幅度减少需要创建的对象数量,从而减少开销高系统资源的利用率,从而降低系统的压力是非常有用的。我们的享元模式就是用来做这个事情的。

1.1、什么是享元模式

  • 定义

​ 享元模式:“享元”,顾名思义就是被共享的单元,是一种结构型设计模式。以共享的方式高效的支持大量细粒度的对象的重用。

​ 享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。

​ 内部状态:可以共享,不会随环境变化而改变。

​ 外部状态:不可以共享,会随环境变化而改变。

​ 比如,连接池中的连接对象,保存在连接对象中的用户名、密码、URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态,而不需要把这些数据保留在每个对象中。而当每个连接要被回收利用时,我们需要将它标记为不可用状态,这些为外部状态。

享元模式的意图在于:通过共享来缓存对象,降低内存消耗,前提是享元的对象是不可变对象。

​ 享元模式的结构:

​ 抽象享元(Flyweight)角色:是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。

​ 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。

​ 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。

​ 享元工厂(Flyweight Factory)角色:负责创建并管理享元对象,享元池一般设计成键值对形式。

1.2、享元模式的优缺点

  • 优点

​ 通过共享来缓存对象,减少了系统中细粒度对象给内存带来的压力。

  • 缺点

​ 需要将一些不能共享的状态外部化,这将增加程序的复杂性,以时间换空间。

1.3、创建方式

​ 我们以一片森林为例,森林中的树木分布在各个方位,

1)创建享元类

//享元类
public abstract class TreeFlyWeight {

 String name; //树的名字可以共享,可能一大片都是同种树
    Color color; //树的颜色可以共享,大多数的树叶都是绿色
    
    abstract void draw(Graphics g,UnsharableFlyweight zb)//描绘树

 public TreeFlyWeight(String name, Color color) {
  super();
  this.name = name;
  this.color = color;
 }

}

2)非享元类

//非享元类
public class UnsharableFlyweight {
 private int x,y; //外部状态坐标,树的坐标是不同的

 public UnsharableFlyweight(int x, int y) {
  super();
  this.x = x;
  this.y = y;
 }

 public int getX() {
  return x;
 }

 public void setX(int x) {
  this.x = x;
 }

 public int getY() {
  return y;
 }

 public void setY(int y) {
  this.y = y;
 }
 
}

3)具体享元

public class ConcreteTree extends TreeFlyWeight{

 public ConcreteTree(String name, Color color) {
  super(name, color);
 }

 @Override
 void draw(Graphics g, UnsharableFlyweight zb) {
  g.setColor(Color.BLACK); //树干的颜色
  g.fillRect(zb.getX() - 1, zb.getY(), 35); //(矩形填充)树干是黑色
        g.setColor(color);  //设置树叶的颜色
        g.fillOval(zb.getX() - 5, zb.getY() - 101010); //绘制树叶的形状(多边形),
 }
 
}

4)享元工厂

//享元工厂
public class TreeFlyWeightFactory {
 // 享元池
 static Map<String, TreeFlyWeight> treeTypes = new HashMap<String, TreeFlyWeight>();

 public static TreeFlyWeight getTreeType(String name, Color color) {
  TreeFlyWeight result = treeTypes.get(name);
  if (result == null) {
   result = new ConcreteTree(name, color);
   treeTypes.put(name, result);
  }
  return result;
 }
}

5)客户端

public class Client extends JFrame{
 
 public static void main(String[] args) {
  testDemo1(); //同一种树的名称和颜色共享
  
  //创建一片森林,1000棵树(包含两种类别的树)
  new Client();
  
 }
 
 public Client(){
  this.setSize(500,500); //森林画布大小
  this.setVisible(true);
 }
 
 static void testDemo1(){
  TreeFlyWeight treeTypes1 = TreeFlyWeightFactory.getTreeType("樟树", Color.GREEN);
  TreeFlyWeight treeTypes2 = TreeFlyWeightFactory.getTreeType("樟树", Color.GREEN);
  System.out.println(treeTypes1==treeTypes2);//true;
 }
 
 @Override
    public void paint(Graphics graphics) {
  for (int i = 0; i < 500; i++) { //每种树500棵
   TreeFlyWeight treeTypes1 = TreeFlyWeightFactory.getTreeType("樟树", Color.GREEN); //这种在for循环中并不会大量创建
   TreeFlyWeight treeTypes2 = TreeFlyWeightFactory.getTreeType("枫树", Color.YELLOW);
   treeTypes1.draw(graphics, new UnsharableFlyweight(random(0,500),random(0,500)));
   treeTypes2.draw(graphics, new UnsharableFlyweight(random(0,500),random(0,500)));
  }
    }
 
 private static int random(int min, int max) {
        return min + (int) (Math.random() * ((max - min) + 1));
    }
}
  • 案例效果

1.4、总结及建议

​ 享元模式可以使你共享地访问那些大量出现的细粒度的对象,有人会觉得这不是和单例模式很像吗?只不过是享元模式有多个对象共享。但是他们的设计意图两个不同的出发点,享元模式是为了对象复用,节省内存;而单例模式则是为了限制对象的个数,享元对象不可变,而单例对象是可变的。

应用场景:

1)应用程序需要产生大量类似的对象,包含可以在多个对象之间提取和共享的重复状态时可以使用享元模式。

JDK中享元模式的应用

java.lang.String

java.lang.Integer#valueOf(int)