乐趣区

关于java:我所知道设计模式之享元模式

前言介绍


接下里介绍的是 Java 的设计模式之一:享元模式

咱们还是以一个问题进行开展,引入享元模式

当初有小型的外包我的项目,给客户 A 做一个产品展现网站

客户 A 的敌人们感觉成果不错,也心愿做这样的产品展现网站

然而要求都有些不同:

1. 敌人 B 要求以新闻的模式公布
2. 敌人 C 要求以博客的模式公布
3. 敌人 D 要求以微信公众号的模式公布

那么你该怎么解决呢?

一、传统形式解决问题

最简略的解决形式是:

1. 间接复制粘贴一份,而后依据客户不同要求,进行定制批改

2. 将网站复制到不同的服务器上,进行定制改良

传统计划解决网站展示我的项目 - 问题剖析

须要的 网站构造类似度很高 ,而且都不是高访问量网站,如果分成多个虚拟空间来解决,相当于 一个雷同网站的实例对象很多,造成服务器的资源节约

解决思路:整合到一个网站中,共享其相干的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都能够达成共享,缩小服务器资源

对于代码来说,因为是一份实例,保护和扩大都更加容易

下面的解决思路就能够应用 享元模式 来解决

二、什么是享元模式

享元模式(Flyweight Pattern)也叫 蝇量模式: 使用共享技术无效地反对大量细粒度的对象

罕用于零碎底层开发,解决零碎的性能问题。

像数据库连接池,外面都是创立好的连贯对象,在这些连贯对象中有咱们须要的则间接拿来用,防止从新创立,如果没有咱们须要的,则创立一个

享元模式可能 解决反复对象的内存节约的问题,当零碎中有大量类似对象,须要缓冲池时。

不需总是创立新对象,能够从缓冲池里拿。这样能够升高零碎内存,同时提高效率

享元模式经典的利用场景就是 池技术了,String 常量池、数据库连接池、缓冲池等等都是享元模式的利用,享元模式是池技术的重要实现形式

比如说咱们当初有一个棋盘,有四个用户、A、B、C、D

如果每个用户观看棋盘,我都返回一个棋盘

假若一个棋盘有三百颗棋子,那么就有一千二百个棋子耗费,是不是很节约

如果说当 A 用户去观看棋盘的时候,进行缓存 那么 B、C、D 间接返回即可

是不是就能够防止类似反复对象造成的内存节约问题

享元模式原理类图剖析

FlyWeight:形象的享元角色, 他是产品的抽象类, 同时 定义出对象的内部状态和外部状态的接口或实现

ConcreteFlyWeight: 具体的享元角色,是具体的产品类,实现形象角色定义相干业务

UnSharedConcreteFlyWeight: 是不可共享的角色,个别不会呈现在享元工厂。

FlyWeightFactory:享元工厂类,用于构建一个池容器(汇合),同时提供从池中获取对象办法

什么是外部状态和内部状态

享元模式提出了两个要求:细粒度和共享对象

这里就波及到外部状态和内部状态了,行将对象的信息分为两个局部:外部状态和内部状态

比方围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有 黑白两色 ,跳棋色彩多一点,所以 棋子色彩就是棋子的外部状态

而各个棋子之间的差异就是地位的不同,当咱们落子后,落子色彩是定的,但地位是变动的 ,所以棋子 坐标就是棋子的内部状态

外部状态指对象 共享进去的信息 ,存储在享元 对象外部且不会随环境的扭转而扭转

内部状态指对象 得以依赖的一个标记,是随环境扭转而扭转的、不可共享的状态

示例阐明

举个例子:围棋实践上有 361 个空位能够放棋子,每盘棋有可能有两三百个棋子对象产生

因为内存空间无限,一台服务器很难反对更多的玩家玩围棋游戏

如果用享元模式来解决棋子,那么棋子对象就能够缩小到只有两个实例,这样就很好的解决了对象的开销问题

三、应用享元模式解决问题

那么依据咱们的思路类图,先创立抽象类 WebSite

abstract class WebSite {
    // 形象办法
    public abstract void use();}

接下来咱们创立不同网站的实现类继承抽象类 ConcreteWebSite

// 具体网站
class ConcreteWebSite extends WebSite {

    // 共享的局部,外部状态
    private String type = ""; // 网站公布的模式(类型)

    // 结构器
    public ConcreteWebSite(String type) {this.type = type;}

    @Override
    public void use() {System.out.println("网站的公布模式为:" + type + "");
    }
}

接下来咱们创立工厂,依据须要压缩返回一个具体的网站

// 网站工厂类,依据须要返回压一个网站
class WebSiteFactory {

    // 汇合,充当池的作用
    private HashMap<String, ConcreteWebSite> pool = new HashMap<>();

    // 依据网站的类型,返回一个网站, 如果没有就创立一个网站,并放入到池中, 并返回
    public WebSite getWebSiteCategory(String type) {if (!pool.containsKey(type)) {
            // 没有则就创立一个网站,并放入到池中
            pool.put(type, new ConcreteWebSite(type));
        }
        return (WebSite) pool.get(type);
    }

    // 获取网站分类的总数 (池中有多少个网站类型)
    public int getWebSiteCount() {return pool.size();
    }
}

接下来咱们应用 demo 领会一下不同类型返回不同的网站

public static void main(String[] args) {

        // 创立一个工厂类
        WebSiteFactory factory = new WebSiteFactory();

        // 客户要一个以新闻模式公布的网站
        WebSite webSite1 = factory.getWebSiteCategory("新闻");

        webSite1.use();

        // 客户要一个以博客模式公布的网站
        WebSite webSite2 = factory.getWebSiteCategory("博客");
        webSite2.use();
        
        System.out.println("网站的分类共 =" + factory.getWebSiteCount());
    }
}

运行后果如下:网站的公布模式为: 新闻
网站的公布模式为: 博客
网站的分类共 =2

然而咱们有没有思考到一个问题,这个网站具体是哪些用户在应用?

咱们的外部状态 类型是有了,然而内部状态还未有,接下来咱们创立

class User {

    private String name;

    public User(){}

    public User(String name) {this.name = name;}

    public String getName() {return name;}

    public void setName(String name) {this.name = name;}
}

抽象类办法 WebSite 增加多一个用户应用网站的办法

abstract class WebSite {
    // 形象办法
    public abstract void use(User user);
}

给予咱们网站增加用户应用办法

// 具体网站
class ConcreteWebSite extends WebSite {

    // 省略其余关键性代码....
    @Override
    public void use(User user) {System.out.println("网站的公布模式为:" + type + "在应用中 .. 使用者是" + user.getName());
    }
}

接下来咱们再应用 demo 就能够看到外部类 具体使用者了

public static void main(String[] args) {

    // 创立一个工厂类
    WebSiteFactory factory = new WebSiteFactory();

    // 客户要一个以新闻模式公布的网站
    WebSite webSite3 = factory.getWebSiteCategory("新闻");
    webSite3.use(new User("smith"));

    // 客户要一个以博客模式公布的网站
    WebSite webSite4 = factory.getWebSiteCategory("博客");
    webSite4.use(new User("king"));

    System.out.println("网站的分类共 =" + factory.getWebSiteCount());
}

运行后果如下:网站的公布模式为: 新闻 在应用中 .. 使用者是 smith
网站的公布模式为: 博客 在应用中 .. 使用者是 king
网站的分类共 =2

四、JDK 中享元模式源码剖析

接下来咱们应用其中剖析 JDK Integer 中的享元模式

请先看一个示例,咱们来看看 demo

public static void main(String[] args) {Integer x = Integer.valueOf(127);
    Integer y = new Integer(127);
    Integer z = Integer.valueOf(127);
    Integer w = new Integer(127);

    System.out.println(x.equals(y));
    System.out.println(x == y);
    System.out.println(x == z);
    System.out.println(w == x);
    System.out.println(w == y);
}

那么输入后果会是什么呢?

true
false
true
false
false

那为什么会造成这样的后果呢?为什么 x 与 z 为 true 呢?

先让咱们看看 Integer valueOf 这个办法,进行剖析把

public final class Integer extends Number implements Comparable<Integer> {

    
    @Native public static final int   MIN_VALUE = 0x80000000;
    
    @Native public static final int   MAX_VALUE = 0x7fffffff;
    
    // 省略其余关键性代码.....
    public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
}

是否与咱们之前的工厂很类似呢?

如果 i >= IntegerCache.low && i <= IntegerCache.high 则 cache 返回

如果不在这个范畴以内,那么则 new 返回

那么 IntegerCache.low 是多少呢?IntegerCache.high 又是多少呢?

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

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

    private IntegerCache() {}
}

那么咱们依据源码就会发现,这个范畴就是 - 127 至 128

而咱们的 x = 127,z = 127 合乎这个范畴,从同一个中央返回所以为 true

public static void main(String[] args) {Integer x = Integer.valueOf(200);
    Integer z = Integer.valueOf(200);

    System.out.println(x == z);
}

那么这样的状况下,会返回什么呢?是 true 还是 false?

小结

在 valueOf 办法中,先判断值是否在 IntegerCache 中,如果不在,就创立新的 Integer(new), 否则,就间接从 缓存池返回

valueOf 办法,就应用到享元模式

如果应用 valueOf 办法失去一个 Integer 实例,范畴在 -128 - 127,执行速度比 new 快

五、享元模式的注意事项和细节

在享元模式这样了解,“享”就示意共享,“元”示意对象

零碎中有大量对象,这些 对象耗费大量内存,并且对象的状态大部分能够内部化时,咱们就能够思考选用享元模式

惟一标识码判断,如果在内存中有,则返回这个惟一标识码所标识的对象,用 HashMap/HashTable 存储

享元模式大大 缩小了对象的创立,升高了程序内存的占用,提高效率

享元模式进步了零碎的复杂度。须要拆散出外部状态和内部状态,而内部状态具备固化个性,不应该随着外部状态的扭转而扭转,这是咱们应用享元模式须要留神的中央.

应用享元模式时,留神划分外部状态和内部状态,并且须要有一个工厂类加以控制

享元模式经典的利用场景是须要缓冲池的场景,比方 String 常量池、数据库连接池

参考资料


尚硅谷:设计模式(韩顺平老师):享元模式

Refactoring.Guru:《深刻设计模式》

退出移动版