享元模式-Flyweight

享元模式是运用共享技术有效地支持大量细粒度的对象。一个典型的应用场景就是用来实现各种池了,比如String常量池、数据库连接池、缓冲池等等都是享元模式的应用,所以说享元模式是池技术的重要实现方式。享元模式主要为了解决大量类似对象占用大量内存的现象,因为内存是珍贵的资源,所以我们讲这些相似对象进行归类,提取出相同部分用以共享,这样可以非常明显的节省内存开销,但要记住一个前提,在节省内存的同时,我们是加大了代码运行时间为前提的,所以,有的时候我们需要平衡时间和内存开销。

一、类图

在享元模式结构图中包含如下几个角色:

  • Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
  • FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例并将其存储在享元池中。

二、示例

public class FlyweightClient {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Circle circle = new Circle(getColor());
            circle.setRadius(getRadius());
            circle.setX(getZ());
            circle.setY(getZ());
            circle.draw();
        }
    }

    public static String getColor() {
        String[] colors = {"红色", "橙色", "黄色", "青色", "绿色"};
        Random random = new Random();
        int index = random.nextInt(4);
        return colors[index];
    }

    public static double getRadius() {
        Random random = new Random();
        return random.nextDouble() * 20;
    }

    public static int getZ() {
        Random random = new Random();
        return random.nextInt(100);
    }
}

/**
 * 抽象享元类
 * 这里以画图形举例:比如画圆,加入颜色固定,画圆的方式都是一样的,所不同的就是圆形的位置和圆的半径
 */
interface Shape {
    public void draw();
}

/**
 * 具体享元类
 * 这里创建具体的享元类,类中包含了可以共享的数据和不可共享的数据
 * 例如:可以共享的颜色以及隐形的画圆方式,不可共享的半径和坐标
 */
class Circle implements Shape {

    private int x;
    private int y;
    private double radius;
    private String color;

    public Circle(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;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    public String getColor() {
        return color;
    }

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

    public void draw() {
        System.out.println("画了一个圆心坐标为:(" + this.x + "," + this.y + "),半径为" + this.radius + "," + this.color + "的圆");
    }

}

/**
 * 工厂类:享元模式的具体体现其实是在这一块得到实现的,在这一块我们可以清楚的了解到共享了哪些属性或者数据
 * 在这里假设圆的颜色是固定的,我们只能画固定的几种颜色的圆
 * 在这里例子中对应的共享数据就应该是对应的颜色属性和隐形的不可见的还原的方式,这个在前面交代过,所有圆的
 * 画的方式是一样的
 */
class CircleFactory {
    private static Map<String, Circle> map = new HashMap<>();

    public static Circle getCircle(String color) {
        Circle c = map.get(color);
        if (c == null) {
            c = new Circle(color);
            map.put(color, c);
            return c;
        }
        return c;
    }
}

三、Java中的享元模式

Java中的String

在Java语言中,String类型就是使用了享元模式。String对象是不变对象,对象一旦创建就不可改变。在JVM内部,String对象都是共享的,如果两个String对象所包含的字符串相同的话,JVM实际上只创建一个String对象提供两个引用,从而实现String对象的共享。String的intern()方法给出这个字符串在共享池中的唯一实例。

在Java中字符串常量都是存储在常量池中的,Java会确保一个字符串常量在常量池中只有一个拷贝。String a = "abc",其中"abc"就是一个字符串常量。

public class Test {
    public static void main(String[] args) {
        String a = "abc";
        String b = "abc";
        System.out.println(a==b);
    }
}

Java中的Integer

实际上Byte、Short、Integer、Long、Character等类都使用了缓存机制:复用对象。这种方式类似于享元模式。对于Integer类型,JVM会将值在[-128,127]之间的对象进行共享。关于Integer类中的缓存机制可以参考Integer类的缓存机制

 

 ……更多设计模式的内容,可以访问Refactoring.Guru
posted @ 2019-10-29 22:55  静水楼台/Java部落阁  阅读(456)  评论(0编辑  收藏  举报