Java设计模式系列十四(享元模式)

前言java

秋雨绵绵,周末午后,小区凉亭。面试

李大爷:"你来了。"编程

我:"我来了。"设计模式

李大爷:"我知道你会来的!"服务器

我:"我固然会来,你固然知道,不然一天前你又怎会让我走?"ide

我目光重落,再次凝视着他,过了好久,才缓缓道:"如今一天已过去。性能

李大爷:"整整一天。"测试

我:"好长的一天。"this

李大爷:"好短的一天。"spa

我:"虽然我明知今日必死但我不是那种等死的人。"

李大爷:"如今你的事是否已作完,你的心愿已了。"

秋雨依旧绵绵,行人寥寥。

李大爷:"出招吧!"

我:"一天前,我败在你的手下。"

李大爷淡淡道:"也许你本不应败的,只惋惜你的人太年轻,棋法却用老了。"

我:"你借我一天时光,让我去作我本身想作的事,如今一天已过去,我……"

李大爷道:"你是来送死的。"

我:"不错,我正是来送死的。"

我:"我既然来了,就已抱定必死之心。"

李大爷道:"你不想再多活一天?"

我突然仰面而笑,道:"大丈夫生于世,若不能下棋赢遍小区大爷,广场舞浪过广场大妈,快意恩仇,就算再多活一百年,也是生不如死"。

李大爷打断了个人话,冷冷道:"你本不是个多话的人,我也不是来跟你说话的,你只求速死?"

我:"是。"

李大爷长长吐出口气,闭上眼瞪,道:"请!请出手,今天仍是你先走第一步。"

上面是我和小区李大爷的故事,我既然还能出如今这儿写公众号,大家应该知道故事的结局了,没错,我赢了那一盘,下个目标就是小区张大爷了,嘿嘿!不少朋友应该也都玩过五子棋或者围棋,几百颗棋子,分为黑白两色,棋差一招者,短短数十回合以后就缴械投降;旗鼓至关者你来我往,攻防有序,几百回合不见胜负,满盘黑白错落有致。

好了,言归正传,试想一下,若是咱们要用程序来设计围棋游戏,黑子181枚,白子180枚,那咱们是否是每下一个子时,都要去new一个棋子对象呢?Java是一门面向对象语言,咱们都知道若是在内存中不停的new新对象时,当对象数量太多时,又回收不及时时,将致使运行代价太高,带来性能降低等问题。那咱们怎么去解决这类问题呢?下面,将为你们讲解本篇的重点,Java设计模式之——享元模式。

 

什么是享元模式

为了方便理解,咱们先来看一下享元模式的两种状态:

  • 内部状态(Intrinsic State):是存储在享元对象内部而且不会随环境改变而改变的状态,所以内部状态能够共享。

  • 外部状态(Extrinsic State):是随环境改变而改变的、不能够共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被建立以后,在须要使用的时候再传入到享元对象内部。一个外部状态与另外一个外部状态之间是相互独立的。

享元模式将一个对象的状态分为内部状态和外部状态,其中,两者是相互独立的,共享相同的内部状态,经过设置不一样的外部状态来改变对象的特征,让一个对象拥有不一样的特征,但内部状态始终是共享的,不可改变的。也就是,改变外部状态不会引发内部状态改变。

能够把围棋想象成享元模式,他们的大小、形状、颜色是内部状态,棋子的位置是外部状态,这样在设计时,只须要设置黑白棋子两个对象,黑棋共享黑色的内部状态,白棋共享白色的内部状态,棋盘上每一个棋子的位置就是他们的外部状态,围棋盘361个交叉点位置,棋子每落一个位置(外部状态),都不会改变棋子的颜色(内部状态)。这样是否是好理解一点。

享元模式通常会结合工厂模式使用,目的是为了建立一个享元工厂来负责维护享元池(Flyweight Pool),享元池里存放的是具备相同内部状态的享元对象。在实际的平常业务的变幻无穷中,可以共享的内部状态是不多的,因此享元对象通常都设计为较小的对象,包含的内部状态也不多,这种对象也成为细粒度对象。

如今咱们来看一下享元模式的英文定义:

Flyweight Pattern: Use sharing to support large numbers of fine-grained objects efficiently.

翻译过来就是:运用共享技术有效地支持大量细粒度对象的复用。(Flyweight我不也不懂为何国内都翻译成享元,没找到资料,多是根据这个模式的做用和特性翻译来的,若是有知道的朋友烦请文末留言告知一声,谢谢!)

再看一下国内对享元模式的解释:

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少许的对象,而这些对象都很类似,状态变化很小,能够实现对象的屡次复用。因为享元模式要求可以共享的对象必须是细粒度对象,所以它又称为轻量级模式,它是一种对象结构型模式。

简而言之:享元模式的目的就是经过共享不变的部分,达到减小对象数量并节约内存的目的。

 

享元模式的四个角色

  • Flyweight(抽象享元类):接口或抽象类,声明公共方法,这些方法能够向外界提供对象的内部状态,设置外部状态。

  • ConcreteFlyweight(具体享元类):实现了抽象享元类,其实例称为享元对象。必须是可共享的,须要封装享元对象的内部状态;。

  • UnsharedConcreteFlyweight(非共享具体享元类):非共享的享元实现对象,并非全部的享元对象均可以共享,非共享的享元对象一般是享元对象的组合对象。

  • FlyweightFactory(享元工厂类):享元工厂,主要用来建立并管理共享的享元对象,并对外提供访问共享享元的接口。它针对抽象享元类编程,将各类类型的具体享元对象存储在一个享元池中,享元池通常设计为一个存储“键值对”的集合(也能够是其余类型的集合),能够结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已建立的实例或者建立一个新的实例(若是不存在的话),返回新建立的实例并将其存储在享元池中。

 

享元模式的UML图

 

代码实例

我就不用我和李大爷下棋的例子了,以避免在他老大(幼小)的心灵上留下创伤。关于棋子的案例,网上也有不少版本,你们感兴趣的能够本身去看。下面咱们用王者荣耀游戏来举例。咱们知道,在一局对战赛里,每隔几分钟就会出现一波小兵和超级兵,小兵都长的如出一辙,超级兵也是,若是王者团队在设计小兵出场的时候,每出来一个小兵,就new一个小兵对象,那么在这个几百万甚至更多人同时在线角逐的游戏里,服务器压力根本就顶不住,还能不能好好的、流畅的、愉快的上分了,小学生放学后早就乖乖在家作做业了。

那么怎样设计呢?咱们能够将小兵的体征、装配、兵种做为内部状态,而后它们在地图上出击的方向做为外部状态,这样不管小兵从哪一个方向出击(外部状态怎样改变),都不会改变小兵的体征和兵种(内部状态),这样咱们在开发时,每一个兵种只要有一个享元对象就能够了。来看代码:

一、编写抽象享元类

package com.weiya.mazhichu.designpatterns.flyweight;

/**
 * <p class="detail">
 * 功能:抽象享元类
 * </p>
 *
 * @author Moore
 * @ClassName Soldier flyweight.
 * @Version V1.0.
 * @date 2019.09.03 21:06:52
 */
public interface SoldierFlyweight {
    /**
     * <p class="detail">
     * 功能:敌军出击方法
     * </p>
     *
     * @param direction :
     * @author Moore
     * @date 2019.09.03 21:06:52
     */
    public void attack(String direction);
}

二、编写具体享元类

package com.weiya.mazhichu.designpatterns.flyweight;

/**
 * <p class="detail">
 * 功能:具体享元类
 * </p>
 *
 * @author Moore
 * @ClassName Concrete solider flyweight.
 * @Version V1.0.
 * @date 2019.09.04 09:45:41
 */
public class ConcreteSoliderFlyweight implements SoldierFlyweight {

    // 内部状态
    private String soliderType;

    public ConcreteSoliderFlyweight(String soliderType) {
        this.soliderType = soliderType;
    }

    @Override
    public void attack(String direction) {
        if("normal".equals(soliderType)){
            System.out.println("普通兵加入战场");
        }
        if("super".equals(soliderType)){
            System.out.println("超级兵加入战场");
        }
        System.out.println("出击方向:"+direction);
    }
}

三、编写享元工厂

package com.weiya.mazhichu.designpatterns.flyweight;

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

/**
 * <p class="detail">
 * 功能:享元工厂
 * </p>
 *
 * @author Moore
 * @ClassName Soldier fly weight factory.
 * @Version V1.0.
 * @date 2019.09.03 21:06:58
 */
public class SoldierFlyWeightFactory {

    //工厂实例
    private static SoldierFlyWeightFactory INSTANCE;
    // 享元池
    private static Map<String,SoldierFlyweight> soldierMap = new HashMap<String,SoldierFlyweight>();

    private SoldierFlyWeightFactory(){
        SoldierFlyweight normalSoldier = new ConcreteSoliderFlyweight("normal");
        soldierMap.put("normal",normalSoldier);
        SoldierFlyweight superSolider = new ConcreteSoliderFlyweight("super");
        soldierMap.put("super",superSolider);
    }

    /**
     * <p class="detail">
     * 功能:获取工厂实例
     * </p>
     *
     * @return soldier fly weight factory
     * @author Moore
     * @date 2019.09.03 21:07:02
     */
    public static SoldierFlyWeightFactory getInstance(){
        if(INSTANCE == null){
            INSTANCE = new SoldierFlyWeightFactory();
            return INSTANCE;
        }
        return INSTANCE;
    }

    /**
     * <p class="detail">
     * 功能:获取享元对象
     * </p>
     *
     * @param soliderType :
     * @return soldier flyweight
     * @author Moore
     * @date 2019.09.03 21:07:02
     */
    public SoldierFlyweight getSolider(String soliderType){
        return soldierMap.get(soliderType);
    }

    /**
     * <p class="detail">
     * 功能:获取享元池对象数量
     * </p>
     *
     * @return int
     * @author Moore
     * @date 2019.09.03 21:07:02
     */
    public int getSoliderSize(){
        return soldierMap.size();
    }

}

四、客户端测试

package com.weiya.mazhichu.designpatterns.flyweight;

/**
 * <p class="detail">
 * 功能:
 * </p>
 *
 * @author Moore
 * @ClassName Honour of kings test.
 * @Version V1.0.
 * @date 2019.09.03 21:06:44
 */
public class HonourOfKingsTest {
    public static void main(String[] args) {

        System.out.println("敌军还有五秒到达战场!");

        SoldierFlyWeightFactory factory = SoldierFlyWeightFactory.getInstance();

        SoldierFlyweight soldier1 = factory.getSolider("normal");
        SoldierFlyweight soldier2 = factory.getSolider("normal");
        SoldierFlyweight soldier3 = factory.getSolider("normal");

        soldier1.attack("上路");
        soldier2.attack("中路");
        soldier3.attack("下路");

        System.out.println(soldier1 == soldier2);
        System.out.println(soldier2 == soldier3);

        System.out.println("--------------------------");
        System.out.println("主宰已被击败!");

        SoldierFlyweight soldier4 = factory.getSolider("super");
        SoldierFlyweight soldier5 = factory.getSolider("super");
        SoldierFlyweight soldier6 = factory.getSolider("super");

        soldier4.attack("上路");
        soldier5.attack("中路");
        soldier6.attack("下路");

        System.out.println("对方法师残血,被超级兵打死...");
        System.out.println(soldier4 == soldier5);
        System.out.println(soldier5 == soldier6);

        System.out.println("--------------------------");
        System.out.println("该案例一共生成对象:" + factory.getSoliderSize() + "个");

    }
}

查看运行结果:

能够看出,咱们一共派出了6个小兵,其中3个普通兵,3个超级兵,可是享元池中只有两个对象(一个普通兵、一个超级兵对象),也就是说,不管派出多少普通兵或者超级兵,不管它们要从哪一路出击,都不会影响兵的内部状态,从而让整个系统的对象大大减小,减小内存消耗,不卡就不影响游戏体验,小学生又能够开心快乐的出来坑人了,可是要以学业为重哦!

 

享元模式扩展

在上面的实例中,咱们主要讲的是具体的享元对象,也就是全部的享元对象都是必须共享的。可是享元模式的四个角色中还有一个非共享的享元实现对象,什么意思呢,顾名思义就是享元对象不必定要共享,可是它一般是做为享元对象的组合对象来使用。从这个层面来讲,咱们又把享元对象分为:

  • 单纯享元模式:在单纯享元模式中,全部的享元对象都是能够共享的,即全部抽象享元类的子类均可共享,不存在非共享具体享元类。

  • 复合享元模式:将一些单纯享元使用组合模式加以组合,能够造成复合享元对象,这样的复合享元对象自己不能共享,可是它们能够分解成单纯享元对象,然后者则能够共享。(复合的享元对象实现了抽象享元类,它的实例就是非共享的享元实现对象)

复合享元模式中,组成复合享元对象的每一个单纯享元对象拥有本身的内部状态,而每一个单纯享元对象的外部状态都和复合享元对象的外部状态相同。因此复合享元模式能够对多个单纯享元对象设置相同的外部状态, 这也是复合享元模式的应用场景。

单纯的享元模式我就再也不赘述了,看上面的棋子或者农药的实例,下面主要说一下组合享元模式,以及它为什么非共享,来看代码:

一、编写复合享元角色类

package com.weiya.mazhichu.designpatterns.flyweight;

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

/**
 * <p class="detail">
 * 功能: 复合享元角色类(非共享享元实现对象)
 * </p>
 *
 * @author Moore
 * @ClassName Concrete composite solider flyweight.
 * @Version V1.0.
 * @date 2019.09.04 10:56:11
 */
public class ConcreteCompositeSoliderFlyweight implements SoldierFlyweight {

    private static Map<String,SoldierFlyweight> soldierMap = new HashMap<String,SoldierFlyweight>();


    /**
     * <p class="detail">
     * 功能: 增长单纯享元对象
     * </p>
     *
     * @param soliderType :
     * @param flyweight   :
     * @author Moore
     * @date 2019.09.04 10:56:11
     */
    public void add(String soliderType,SoldierFlyweight flyweight){
        soldierMap.put(soliderType,flyweight);
    }

    /**
     * <p class="detail">
     * 功能: flyWeights是单纯享元对象的集合,它们具备相同的外部状态extrinsicState,
     *     调用的时候使用循环调用单纯享元对象的attack方法
     * </p>
     *
     * @param direction :
     * @author Moore
     * @date 2019.09.03 21:06:52
     */
    @Override
    public void attack(String direction) {
        SoldierFlyweight flyweight = null;
        for(String str : soldierMap.keySet()){
            flyweight = soldierMap.get(str);
            flyweight.attack(direction);
        }
    }

    /**
     * 移除单纯享元对象.
     * @param soliderType
     */
    private void remove(String soliderType) {
        soldierMap.remove(soliderType);
    }
}

二、修改后的享元工厂角色类

package com.weiya.mazhichu.designpatterns.flyweight;

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

/**
 * <p class="detail">
 * 功能:享元工厂
 * </p>
 *
 * @author Moore
 * @ClassName Soldier fly weight factory.
 * @Version V1.0.
 * @date 2019.09.03 21:06:58
 */
public class SoldierFlyWeightFactory {

    //工厂实例
    private static SoldierFlyWeightFactory INSTANCE;
    // 享元池
    private static Map<String,SoldierFlyweight> soldierMap = new HashMap<String,SoldierFlyweight>();

    private SoldierFlyWeightFactory(){
        SoldierFlyweight normalSoldier = new ConcreteSoliderFlyweight("normal");
        soldierMap.put("normal",normalSoldier);
        SoldierFlyweight superSolider = new ConcreteSoliderFlyweight("super");
        soldierMap.put("super",superSolider);
    }

    /**
     * <p class="detail">
     * 功能:获取工厂实例
     * </p>
     *
     * @return soldier fly weight factory
     * @author Moore
     * @date 2019.09.03 21:07:02
     */
    public static SoldierFlyWeightFactory getInstance(){
        if(INSTANCE == null){
            INSTANCE = new SoldierFlyWeightFactory();
            return INSTANCE;
        }
        return INSTANCE;
    }

    /**
     * <p class="detail">
     * 功能:获取享元对象(单纯享元工厂方法)
     * </p>
     *
     * @param soliderType :
     * @return soldier flyweight
     * @author Moore
     * @date 2019.09.03 21:07:02
     */
    public SoldierFlyweight getSolider(String soliderType){
        return soldierMap.get(soliderType);
    }


    /**
     * <p class="detail">
     * 功能:复合享元工厂方法
     * </p>
     *
     * @param compositeSoliderTypes :
     * @return soldier flyweight
     * @author Moore
     * @date 2019.09.04 11:06:24
     */
    public SoldierFlyweight getCompositeSolider(List<String> compositeSoliderTypes){
        ConcreteCompositeSoliderFlyweight compositeFlyweight = new ConcreteCompositeSoliderFlyweight();
        for(String soliderType : compositeSoliderTypes){
            compositeFlyweight.add(soliderType,this.getSolider(soliderType));
        }
        return compositeFlyweight;
    }

    /**
     * <p class="detail">
     * 功能:获取享元池对象数量
     * </p>
     *
     * @return int
     * @author Moore
     * @date 2019.09.03 21:07:02
     */
    public int getSoliderSize(){
        return soldierMap.size();
    }
}

三、编写测试类

package com.weiya.mazhichu.designpatterns.flyweight;

import java.util.ArrayList;
import java.util.List;

/**
 * <p class="detail">
 * 功能: 测试单纯享元模式和复合享元模式
 * </p>
 *
 * @author Moore
 * @ClassName Flyweight test.
 * @Version V1.0.
 * @date 2019.09.04 11:08:51
 */
public class FlyweightTest {
    public static void main(String[] args) {

        SoldierFlyWeightFactory factory = SoldierFlyWeightFactory.getInstance();

        String soliderType = "normal";
        SoldierFlyweight soldierFlyweight1 = factory.getSolider(soliderType);
        SoldierFlyweight soldierFlyweight2 = factory.getSolider(soliderType);
        soldierFlyweight1.attack("上路");
        soldierFlyweight2.attack("中路");


        System.out.println("---------------------------------");

        List<String> compositeSoliderType = new ArrayList<String>();
        compositeSoliderType.add("normal");
        compositeSoliderType.add("super");
        compositeSoliderType.add("normal");
        compositeSoliderType.add("super");
        compositeSoliderType.add("normal");


        SoldierFlyweight compositeSoliderFlyeweight1 = factory.getSolider(compositeSoliderType);
        SoldierFlyweight compositeSoliderFlyeweight2 = factory.getSolider(compositeSoliderType);
        compositeSoliderFlyeweight1.attack("上路");
        compositeSoliderFlyeweight2.attack("中路");

        System.out.println("---------------------------------");
        System.out.println("单纯享元模式是否共享对象:" + (soldierFlyweight1 == soldierFlyweight2));
        System.out.println("复合享元模式是否共享对象:" + (compositeSoliderFlyeweight1 == compositeSoliderFlyeweight2));

    }
}

查看运行结果:

结合运行结果,再来逐字逐句看一下这一段,你应该就能有所体会了。

复合享元模式中,组成复合享元对象的每一个单纯享元对象拥有本身的内部状态,而每一个单纯享元对象的外部状态都和复合享元对象的外部状态相同。因此复合享元模式能够对多个单纯享元对象设置相同的外部状态, 这也是复合享元模式的应用场景。

 

复合享元模式UML图

 

享元模式总结

使用场景

  • 系统有大量类似或者相同对象。因为这类对象的大量使用,形成内存的大量耗费。

  • 须要缓冲池的场景,(享元池,也就是在须要屡次使用享元对象的时候)。

  • 对象的大部分状态均可之外部化,能够将这些外部状态传入对象中。

优势

  • 大大减小对象的建立,下降系统的内存,使效率提升。

  • 享元模式的外部状态相对独立,并且不会影响其内部状态,从而使得享元对象能够在不一样的环境中被共享。

缺点

  • 须要分离出外部状态和内部状态,提升了系统的复杂度。

  • 读取享元模式的外部状态会使得运行时间稍微变长。

好了,享元模式就说到这儿了,篇幅稍长,可能须要慢慢多读几遍才能理解。感谢您能看到这儿,但愿能给您稍微有点帮助,我就心满意足了。

 

我不能保证我写的文章都是正确的,可是我能保证都是我本身花时间用心写的,全部的代码示例都是原创,全部的理解都只是我我的理解,不能表明官方权威,因此请各位读者阅读时带着批判的眼光,有选择性的认同,谢谢!

若是以为本文有用,请推荐给您身边的人或者同行关注“码之初”公众号,让咱们一块儿前行,谢谢!

若是发现文章中有问题或者代码里有bug,欢迎留言,请随时批评指正,谢谢!

为了感谢您的关注和喜好,码之初为正在找工做的小伙伴悄悄的送上一批干货,后台发送”面试“关键字,便可领取随机一份面试资料,祝全部小伙伴步步高升,前程似锦!

相关文章
相关标签/搜索