享元模式

模式介绍

享元模式可以理解为一个共享池的概念,即将一个对象缓存起来,下次再用的时候直接在缓存中获取,这样就不用重新创建对象,达到了节省内存、优化程序效率的优点。比如我们常用的String 和 数据库的连接池都是运用了该模式的思想。

设计模式之享元模式-LMLPHP

应用场景

当程序中需要大量的细粒度对象,这些对象内容相似,并且可以按照两种状态区分的时候,就可以使用该模式进行设计。

优缺点

  • 优点

    1. 由于创建的对象都被缓存了起来,所以在此请求相同的对象的时候,就不用在重新创建对象,直接从缓存中获取该对象即可。
    2. 节省了内存开销和程序的效率。
  • 缺点

    1. 增加系统的理解难度。
    2. 需要将对象分为两种状态并且根据这两种状态(内部、外部)来控制是否进行对象的创建。
    3. 需要维护一个共享池,可以理解为工厂模式的实现。

例子介绍

比如我们现在有一个需求,需要在一块画布上随机位置,展示一个圆,这个圆分为四种颜色,分别为 “红、蓝、黄、绿” , 如果使用普通的做法来做就是,客户端发起请求,服务端接收后,根据请求的颜色去创建相应的圆形对象,然后赋予坐标。

package cn.hsh.study.flyweight.ordinary;

/**
 * @author shaohua
 * @date 2021/4/22 19:41
 */
public class Circular {

    private String color;

    private int x;

    private int y;

    public Circular(String color, int x, int y) {
        this.color = color;
        this.x = x;
        this.y = y;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

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

    @Override
    public String toString() {
        return "Circular{" +
                "color='" + color + '\'' +
                ", x=" + x +
                ", y=" + y +
                '}';
    }
}

package cn.hsh.study.flyweight.ordinary;

/**
 * @author shaohua
 * @date 2021/4/22 19:41
 */
public class Client {

    public static void main(String[] args) {

        Circular red = new Circular("red", 1, 10);
        System.out.println(red);
    }
}

结果如下:

设计模式之享元模式-LMLPHP

综上我们可以看到,客户端每次请求的时候都会创建一个新的对象,如果请求了1000次红色圆形那么就会创建1000个圆形对象出来,这个可以发现,除了坐标不同,1000个对象颜色是一样的,这里我们就可以将 颜色 和 坐标,分为内部状态和外部状态,内部状态为颜色可以进行共享但是不可以修改,外部状态就是坐标,不可以共享,但是可以随着客户端的调用而修改。 看代码 ↓

package cn.hsh.study.flyweight;

/**
 * @author shaohua
 * @date 2021/4/20 19:40
 */
public class Circular {

    private String color;

    private int x;
    private int y;

    public String getColor() {
        return color;
    }


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

    public Circular(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Circular{" +
                "color='" + color + '\'' +
                ", x=" + x +
                ", y=" + y +
                '}';
    }
}

首先定义实体对象类,这个可以继承图形抽象类,但是我这里为了节省就直接写死了一个类。

package cn.hsh.study.flyweight.factory;

import cn.hsh.study.flyweight.Circular;

import java.util.HashMap;
import java.util.Map;

/**
 * @author shaohua
 * @date 2021/4/20 19:41
 */
public class FlyWeightFactory {

    private static Map<String, Circular> pools = new HashMap<String, Circular>(16);

    public static Circular factory(String color, int x, int y){
        Circular circular;
        if(pools.containsKey(color)){
            System.out.println("检测到共享池中存在该颜色直接返回");
            circular = pools.get(color);
        } else {
            circular = new Circular(color);
        }
        circular.setX(x);
        circular.setY(y);
        pools.put(color, circular);
        return circular;
    }
}

注意 这里是整个享元模式的核心,也就是我们上面说的工厂类。每次请求的时候使用内部状态(颜色)为key,然后外部状态根据客户端可以随意修改。最后返回。

package cn.hsh.study.flyweight;

import cn.hsh.study.flyweight.factory.FlyWeightFactory;

import java.util.LinkedList;
import java.util.List;

/**
 * @author shaohua
 * @date 2021/4/19 18:45
 */
public class Client {


    public static void main(String[] args) {
        Circular red = FlyWeightFactory.factory("red", 1, 10);
        Circular blue = FlyWeightFactory.factory("blue", 2, 20);
        Circular yellow = FlyWeightFactory.factory("yellow", 3, 30);
        Circular green = FlyWeightFactory.factory("green", 4, 40);
        Circular red1 = FlyWeightFactory.factory("red", 5, 50);
        Circular blue1 = FlyWeightFactory.factory("blue", 6, 60);

        List<Circular> circulars = new LinkedList<Circular>();
        circulars.add(red);
        circulars.add(blue);
        circulars.add(yellow);
        circulars.add(green);
        circulars.add(red1);
        circulars.add(blue1);

        for (Circular circular : circulars) {
            System.out.println(circular.toString());
        }

        System.out.println(red == red1);
        System.out.println(blue == blue1);
    }

}

这是客户端调用,直接看结果 ↓

设计模式之享元模式-LMLPHP

可以看到,每个颜色的圆形在第一次调用的时候都会缓存到共享池中,第二次调用的时候返回的对象是共享池中被创建好了的对象,只是修改了坐标(x, y) 属性而已,对象还是同一个。所以就做到了不用每次请求都去创建一个对象,即节省了内存的开支,也优化了程序的效率。

个人观点

该模式在单线程下可以正常使用,一旦用在并发高的需求上可能会在客户端赋予外部状态的时候出现并发问题,所以该模式需要谨慎使用。

总结

  • 在以下情况下可以使用享元模式

    1. 一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费;
      对象的大部分状态都可以外部化,可以将这些外部状态传入对象中(细粒度对象);
      使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此,应当在多次重复使用享元对象时才值得使用享元模式。
  • 模式的优点

    1. 它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份;
    2. 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
  • 模式的缺点

    1. 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化;
    2. 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
04-23 07:36