前言介绍


接下里介绍的是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 );}

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

truefalsetruefalsefalse

那为什么会造成这样的后果呢?为什么 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:《深刻设计模式》