可以兑现目的的往往复用,例如在电脑内存中存储了三个完全相同大概非凡相像的对象

 
当前我们国家正在拼命提倡构建和谐社会,其中3个很要紧的组成部分就是建设能源节约型社会,“浪费可耻,节俭光荣”。在软件系统中,有时候也会设有财富浪费的气象,例如在总括机内存中存储了多少个完全相同大概分外相像的靶子,如果那些目标的数据太多将导致系统运维代价过高,内存属于计算机的“稀缺能源”,不该用于“随便浪费”,那么是还是不是存在一种技术可以用来节约内存使用空间,完成对这么些相同大概相似对象的共享访问呢?答案是必定,那种技能就是我们本章将要学习的享元形式。

【学习难度:★★★★☆,使用效用:★☆☆☆☆】
一向出处:http://woquanke.com/books/gof/
梳理和学习:https://github.com/BruceOuyang/boy-design-pattern
简书日期: 2018/03/15
简书首页:https://www.jianshu.com/p/0fb891a7c5ed

定义

  享元格局(Flyweight
Pattern):运用共享技术有效地支撑大气细粒度对象的复用。系统只行使少量的对象,而这么些目标都很相像,状态变化很小,可以完毕目的的高频复用。由于享元情势需要可以共享的对象必须是细粒度对象,由此它又称为轻量级方式,它是一种目的结构型模式。
  享元对象能完结共享的根本是分别了其中意况(Intrinsic
State)
表面状态(Extrinsic State)

  • 内部景色是储存在享元对象内部并且不会随环境改观而变更的图景,内部景色可以共享。如字符的始末,不会随外部环境的转变而生成,无论在任何条件下字符“a”始终是“a”,都不会变成“b”。
  • 外表状态是随环境改变而更改的、不可以共享的景况。享元对象的外表状态一般由客户端保存,并在享元对象被创设之后,要求接纳的时候再传播到享元对象内部。2个表面状态与另贰个外表状态之间是互为独立的。如字符的水彩,可以在不一样的地点有不相同的颜料,例如有个别“a”是黑灰的,有的“a”是铁青的,字符的轻重缓急也是如此,有的“a”是五号字,有的“a”是四号字。而且字符的颜料和尺寸是七个独立的外表状态,它们得以独立变化,相互之间没有影响,客户端可以在利用时将表面状态注入享元对象中。
  • 在享元方式中,存储这一个共享实例对象的地点叫作享元池(Flyweight
    Pool)

14.1 围棋棋子的筹划

      Sunny软件公司欲开发一个围棋软件,其界面效果如图14-1所示:

图14-1 围棋软件界面效果图

     
Sunny软件商店开发人士通过对围棋软件拓展解析,发今后围棋棋盘中涵盖多量的黑子和白子,它们的形状、大小都同样,只是现出的地方分裂而已。如若将每三个棋子都看成壹个单身的靶子存储在内存中,将造成该围棋软件在运维时所需内存空间较大,如何降低运作代价、提升系统品质是Sunny公司开发人士需求化解的2个题材。为了消除那个标题,萨妮集团开发人士决定接纳享元情势来设计该围棋软件的棋子对象,那么享元情势是何等兑现节约内存进而做实系统质量的吧?别着急,上面让大家专业进入享元方式的求学。

贯彻目标的复用——享元情势(一)

现阶段大家国家正在拼命倡导打造和谐社会,其中二个很要紧的组成部分就是建设财富节约型社会,“浪费可耻,节俭光荣”。在软件系统中,有时候也会设有财富浪费的状态,例如在微机内存中存储了多少个完全相同或然特别相像的目的,如若那个目的的数目太多将导致系统运维代价过高,内存属于总括机的“稀缺能源”,不应该用于“随便浪费”,那么是否留存一种技术可以用来节约内存使用空间,达成对那几个相同或然相似对象的共享访问呢?答案是肯定,那种技能就是大家本章将要学习的享元方式。

14.1 围棋棋子的陈设性

Sunny软件商店欲开发1个围棋软件,其界面效果如图14-1所示:

图片 1

图14-1 围棋软件界面效果图

Sunny软件商店开发人士通过对围棋软件举办剖析,发将来围棋棋盘中涵盖大批量的黑子和白子,它们的样子、大小都一律,只是现出的地点不一致而已。倘使将每三个棋子都作为八个单独的靶子存储在内存中,将造成该围棋软件在运维时所需内存空间较大,怎么样降低运作代价、升高系统品质是Sunny集团开发人士须求缓解的1个题材。为了化解这一个题材,Sunny企业开发人士决定利用享元格局来设计该围棋软件的棋子对象,那么享元格局是怎么着落到实处节约内存进而加强系统脾气的吧?别着急,下面让我们规范进入享元情势的学习。

14.2 享元格局概述

当一个软件系统在运行时暴发的目的数量太多,将导致运营代价过高,带来系统质量降低等难题。例如在三个文本字符串中存在不少再次的字符,如若每三个字符都用一个单独的靶子来表示,将会占有较多的内存空间,那么大家什么去幸免系统中现身大批量如出一辙或一般的靶子,同时又不影响客户端程序通过面向对象的方式对那一个目的开展操作?享元形式正为杀鸡取蛋这一类难点而诞生。享元情势通过共享技术已毕均等或貌似对象的录取,在逻辑上每二个出现的字符都有二个目的与之对应,然则在情理上它们却共享同2个享元对象,那么些目的足以出现在1个字符串的不等地方,相同的字符对象都指向同三个实例,在享元格局中,存储那么些共享实例对象的地点称为享元池(Flyweight
Pool)。我们得以本着每贰个例外的字符创设四个享元对象,将其位于享元池中,必要时再从享元池取出。如图14-2所示:

图片 2

图14-2 字符享元对象示意图

享元形式以共享的方法很快地支撑大气细粒度对象的任用,享元对象能到位共享的机借使分别了中间情形(Intrinsic
State)和表面状态(Extrinsic
State)。上边将对享元的内部景色和外部状态举行不难的介绍:

(1)
内部景况是储存在享元对象内部并且不会随环境改观而改变的动静,内部情形可以共享。如字符的情节,不会随外部环境的生成而转变,无论在其它环境下字符“a”始终是“a”,都不会成为“b”。

(2)
外部状态是随环境改变而更改的、不可以共享的气象。享元对象的表面状态一般由客户端保存,并在享元对象被创建之后,要求采用的时候再扩散到享元对象内部。多少个表面状态与另贰个外表状态之间是相互独立的。如字符的水彩,可以在不一样的地方有差别的颜料,例如某些“a”是戊午革命的,有的“a”是纯白的,字符的大大小小也是那般,有的“a”是五号字,有的“a”是四号字。而且字符的水彩和大小是三个单身的表面状态,它们得以独自变化,互相之间没有影响,客户端可以在运用时将表面状态注入享元对象中。

正因为有别了里面意况和表面状态,大家可以将有着相同之中景观的对象存储在享元池中,享元池中的对象是可以达成共享的,要求的时候就将指标从享元池中取出,已毕目的的复用。通过向取出的对象注入区其余外表状态,可以博得一密密麻麻相似的靶子,而那个目的在内存中其实只存储一份。

享元形式定义如下:

享元格局(Flyweight
Pattern):运用共享技术可行地帮助大气细粒度对象的复用。系统只利用少量的靶子,而那个目的都很一般,状态变化很小,可以兑现目的的一再复用。由于享元方式须求可以共享的靶子必须是细粒度对象,因而它又称之为轻量级方式,它是一种对象结构型形式。

结构图

图片 3

要素:

  • Flyweight(抽象享元类):经常是三个接口或抽象类,在空虚享元类中扬言了切实可行享元类公共的主意,那一个点子可以向外界提供享元对象的中间数据(内部情况),同时也得以透过这一个措施来设置外部数据(外部状态)。
  • ConcreteFlyweight(具体享元类):它完毕了用空想来安慰自己享元类,其实例称为享元对象;在实际享元类中为其中意况提供了仓储空间。经常大家得以结合单例情势来安顿具体享元类,为每二个切实享元类提供唯一的享元对象。
  • UnsharedConcreteFlyweight(非共享具体享元类):并不是具备的抽象享元类的子类都亟待被共享,无法被共享的子类可统筹为非共享具体享元类;当须求1个非共享具体享元类的靶蛇时方可一贯通过实例化创制。
  • FlyweightFactory(享元工厂类):享元工厂类用于创立并管理享元对象,它针对抽象享元类编程,将各个别型的有血有肉享元对象存储在2个享元池中,享元池一般设计为二个储存“键值对”的汇聚(也得以是其余品种的汇集),可以构成工厂方式进行规划;当用户请求七个现实享元对象时,享元工厂提供一个储存在享元池中已创设的实例或然创建1个新的实例(假使不设有的话),重回新创建的实例并将其储存在享元池中。

class FlyweightFactory {
    //定义一个HashMap用于存储享元对象,实现享元池
       private HashMap flyweights = newHashMap();

       public Flyweight getFlyweight(String key){
              //如果对象存在,则直接从享元池获取
              if(flyweights.containsKey(key)){
                     return(Flyweight)flyweights.get(key);
              }
              //如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
              else {
                     Flyweight fw = newConcreteFlyweight();
                     flyweights.put(key,fw);
                     return fw;
              }
       }
}

class Flyweight {
     //内部状态intrinsicState作为成员变量,同一个享元对象其内部状态是一致的
       private String intrinsicState;

       public  Flyweight(String intrinsicState) {
              this.intrinsicState=intrinsicState;
       }

        //外部状态extrinsicState在使用时由外部设置,不保存在享元对象中,即使是同一个对象,在每一次调用时也可以传入不同的外部状态
       public void operation(String  extrinsicState) {
              ......
       }     
}

14.2 享元情势概述

     
当三个软件系统在运转时发生的目的数量太多,将促成运转代价过高,带来系统质量下降等难题。例如在多个文本字符串中存在重重再度的字符,如若每二个字符都用七个独立的靶子来表示,将会占用较多的内存空间,那么大家什么去幸免系统中冒出大批量一样或貌似的目的,同时又不影响客户端程序通过面向对象的章程对那几个目标开展操作?享元方式正为解决这一类题材而诞生。享元方式通过共享技术落成平等或一般对象的录用,在逻辑上每3个并发的字符都有二个对象与之相应,可是在大体上它们却共享同一个享元对象,那几个目标能够现身在贰个字符串的差距地点,相同的字符对象都指向同二个实例,在享元格局中,存储那几个共享实例对象的地点叫作享元池(Flyweight
Pool)
。大家得以本着每二个两样的字符成立三个享元对象,将其放在享元池中,需求时再从享元池取出。如图14-2所示:

图片 4

图14-2 字符享元对象示意图

     
享元情势以共享的法子快捷地支撑大气细粒度对象的录取,享元对象能不辱任务共享的显借使分别了个中景观(Intrinsic
State)
表面状态(Extrinsic
State)
。上面将对享元的中间景色和外部状态进行简易的介绍:

     
(1)  个中情形是储存在享元对象内部并且不会随环境改变而变更的情景,内部景况可以共享。如字符的始末,不会随外部环境的生成而生成,无论在其余条件下字符“a”始终是“a”,都不会变成“b”。

     
(2)  外表状态是随环境改变而更改的、不可以共享的情况。享元对象的外表状态一般由客户端保存,并在享元对象被创立之后,须求运用的时候再传出到享元对象内部。1个外部状态与另一个表面状态之间是并行独立的。如字符的水彩,可以在不一样的地点有两样的颜色,例如某个“a”是辛丑革命的,有的“a”是蓝绿的,字符的分寸也是那样,有的“a”是五号字,有的“a”是四号字。而且字符的水彩和分寸是八个独立的外表状态,它们得以独立变化,互相之间没有影响,客户端可以在使用时将表面状态注入享元对象中。

     
正因为有别了内部景色和表面状态,大家可以将拥有同等之中景观的对象存储在享元池中,享元池中的对象是足以落成共享的,必要的时候就将对象从享元池中取出,达成目标的复用。通过向取出的目的注入差其余外表状态,可以博得一连串相似的靶子,而那个目标在内存中其实只存储一份。

      享元情势定义如下:

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

 

   
享元形式协会比较复杂,一般结合工厂形式一起行使,在它的构造图中隐含了壹个享元工厂类,其社团图如图14-3所示:

 图片 5

图14-3 享元方式结构图

      在享元情势社团图中隐含如下几个剧中人物:

     
● Flyweight(抽象享元类):日常是1个接口或抽象类,在抽象享元类中扬言了具体享元类公共的办法,那个措施能够向外界提供享元对象的里边数据(内部情形),同时也可以因此这几个格局来安装外部数据(外部状态)。

     
● ConcreteFlyweight(具体享元类):它完毕了抽象享元类,其实例称为享元对象;在切实可行享元类中为其中情状提供了储存空间。日常大家得以组合单例格局来规划具体享元类,为每3个切实享元类提供唯一的享元对象。

     
● UnsharedConcreteFlyweight(非共享具体享元类):并不是拥有的架空享元类的子类都亟需被共享,不可以被共享的子类可统筹为非共享具体享元类;当必要1个非共享具体享元类的靶牛时方可一直通过实例化创建。

     
● FlyweightFactory(享元工厂类):享元工厂类用于创设并管理享元对象,它针对抽象享元类编程,将各个类型的有血有肉享元对象存储在三个享元池中,享元池一般设计为3个储存“键值对”的聚集(也得以是其余品种的集结),可以组合工厂格局展开规划;当用户请求三个有血有肉享元对象时,享元工厂提供一个储存在享元池中已开立的实例可能创设多少个新的实例(假诺不存在的话),重临新创制的实例并将其储存在享元池中。

     
在享元形式中引入了享元工厂类,享元工厂类的作用在于提供两个用来存储享元对象的享元池,当用户需求对象时,首先从享元池中得到,即使享元池中不设有,则成立3个新的享元对象回来给用户,并在享元池中保留该新增对象。典型的享元工厂类的代码如下:

class FlyweightFactory {

    //定义一个HashMap用于存储享元对象,实现享元池

       private HashMap flyweights = newHashMap();

      

       public Flyweight getFlyweight(String key){

              //如果对象存在,则直接从享元池获取

              if(flyweights.containsKey(key)){

                     return(Flyweight)flyweights.get(key);

              }

              //如果对象不存在,先创建一个新的对象添加到享元池中,然后返回

              else {

                     Flyweight fw = newConcreteFlyweight();

                     flyweights.put(key,fw);

                     return fw;

              }

       }

}

     
享元类的设计是享元形式的要领之壹,在享元类中要将中间景观和外部状态分开处理,寻常将里面情形作为享元类的分子变量,而外部状态通过注入的点子充裕到享元类中。典型的享元类代码如下所示:

class Flyweight {

     //内部状态intrinsicState作为成员变量,同一个享元对象其内部状态是一致的

       private String intrinsicState;

      

       public  Flyweight(String intrinsicState) {

              this.intrinsicState=intrinsicState;

       }

      

        //外部状态extrinsicState在使用时由外部设置,不保存在享元对象中,即使是同一个对象,在每一次调用时也可以传入不同的外部状态

       public void operation(String  extrinsicState) {

              ……

       }     

}

完毕目的的复用——享元形式(二)

享元情势结构比较复杂,一般结合工厂方式一起行使,在它的协会图中含有了1个享元工厂类,其社团图如图14-3所示:

图片 6

图14-3 享元形式结构图

在享元方式结构图中包罗如下多少个剧中人物:

  • Flyweight(抽象享元类):平日是二个接口或抽象类,在空洞享元类中扬言了具体享元类公共的主意,这一个主意可以向外侧提供享元对象的其中数据(内部意况),同时也可以经过那么些艺术来安装外部数据(外部状态)。

  • ConcreteFlyweight(具体享元类):它完毕了画饼充饥享元类,其实例称为享元对象;在实际享元类中为其中情状提供了仓储空间。常常我们得以组合单例格局来安排具体享元类,为每二个具体享元类提供唯一的享元对象。

  • UnsharedConcreteFlyweight(非共享具体享元类):并不是有所的架空享元类的子类都亟待被共享,不可以被共享的子类可统筹为非共享具体享元类;当须求二个非共享具体享元类的对象时可以直接通过实例化创造。

  • FlyweightFactory(享元工厂类):享元工厂类用于创立并保管享元对象,它针对抽象享元类编程,将各个类型的具体享元对象存储在3个享元池中,享元池一般设计为三个仓储“键值对”的集结(也可以是其余门类的联谊),可以结合工厂格局展开设计;当用户请求一个具体享元对象时,享元工厂提供二个囤积在享元池中已创立的实例可能创制多个新的实例(即便不设有的话),再次来到新创设的实例并将其储存在享元池中。

在享元方式中引入了享元工厂类,享元工厂类的出力在于提供一个用以存储享元对象的享元池,当用户要求对象时,首先从享元池中收获,借使享元池中不设有,则成立1个新的享元对象回来给用户,并在享元池中保存该新增对象。典型的享元工厂类的代码如下:

class FlyweightFactory {
    //定义一个HashMap用于存储享元对象,实现享元池
    private HashMap flyweights = newHashMap();

    public Flyweight getFlyweight(String key){
           //如果对象存在,则直接从享元池获取
           if(flyweights.containsKey(key)){
                  return(Flyweight)flyweights.get(key);
           }
           //如果对象不存在,先创建一个新的对象添加到享元池中,然后返回
           else {
                  Flyweight fw = newConcreteFlyweight();
                  flyweights.put(key,fw);
                  return fw;
           }
    }
}

享元类的安插是享元形式的主旨情想之1、在享元类中要将其中情状和表面状态分开处理,日常将中间景色作为享元类的分子变量,而外部状态通过注入的办法丰裕到享元类中。典型的享元类代码如下所示:

class Flyweight {
    //内部状态intrinsicState作为成员变量,同一个享元对象其内部状态是一致的
    private String intrinsicState;

    public  Flyweight(String intrinsicState) {
           this.intrinsicState=intrinsicState;
    }

     //外部状态extrinsicState在使用时由外部设置,不保存在享元对象中,即使是同一个对象,在每一次调用时也可以传入不同的外部状态
    public void operation(String  extrinsicState) {
           ......
    }     
}

示例

围棋设计:

图片 7

图片 8

import java.util.*;  

//围棋棋子类:抽象享元类  
abstract class IgoChessman {  
    public abstract String getColor();  

    public void display() {  
        System.out.println("棋子颜色:" + this.getColor());    
    }  
}  

//黑色棋子类:具体享元类  
class BlackIgoChessman extends IgoChessman {  
    public String getColor() {  
        return "黑色";  
    }     
}  

//白色棋子类:具体享元类  
class WhiteIgoChessman extends IgoChessman {  
    public String getColor() {  
        return "白色";  
    }  
}  

//围棋棋子工厂类:享元工厂类,使用单例模式进行设计  
class IgoChessmanFactory {  
    private static IgoChessmanFactory instance = new IgoChessmanFactory();  
    private static Hashtable ht; //使用Hashtable来存储享元对象,充当享元池  

    private IgoChessmanFactory() {  
        ht = new Hashtable();  
        IgoChessman black,white;  
        black = new BlackIgoChessman();  
        ht.put("b",black);  
        white = new WhiteIgoChessman();  
        ht.put("w",white);  
    }  

    //返回享元工厂类的唯一实例  
    public static IgoChessmanFactory getInstance() {  
        return instance;  
    }  

    //通过key来获取存储在Hashtable中的享元对象  
    public static IgoChessman getIgoChessman(String color) {  
        return (IgoChessman)ht.get(color);    
    }  
}  

class Client {  
    public static void main(String args[]) {  
        IgoChessman black1,black2,black3,white1,white2;  
        IgoChessmanFactory factory;  

        //获取享元工厂对象  
        factory = IgoChessmanFactory.getInstance();  

        //通过享元工厂获取三颗黑子  
        black1 = factory.getIgoChessman("b");  
        black2 = factory.getIgoChessman("b");  
        black3 = factory.getIgoChessman("b");  
        System.out.println("判断两颗黑子是否相同:" + (black1==black2));  

        //通过享元工厂获取两颗白子  
        white1 = factory.getIgoChessman("w");  
        white2 = factory.getIgoChessman("w");  
        System.out.println("判断两颗白子是否相同:" + (white1==white2));  

        //显示棋子  
        black1.display();  
        black2.display();  
        black3.display();  
        white1.display();  
        white2.display();  
    }  
}  

带外部状态的消除方案:

图片 9

//坐标类:外部状态类  
class Coordinates {  
    private int x;  
    private int y;  

    public Coordinates(int x,int y) {  
        this.x = x;  
        this.y = y;  
    }  

    public int getX() {  
        return this.x;  
    }  

    public void setX(int x) {  
        this.x = x;  
    }  

    public int getY() {  
        return this.y;  
    }  

    public void setY(int y) {  
        this.y = y;  
    }  
}   

//围棋棋子类:抽象享元类  
abstract class IgoChessman {  
    public abstract String getColor();  

    public void display(Coordinates coord){  
        System.out.println("棋子颜色:" + this.getColor() + ",棋子位置:" + coord.getX() + "," + coord.getY() );    
    }  
}  

class Client {  
    public static void main(String args[]) {  
        IgoChessman black1,black2,black3,white1,white2;  
        IgoChessmanFactory factory;  

        //获取享元工厂对象  
        factory = IgoChessmanFactory.getInstance();  

        //通过享元工厂获取三颗黑子  
        black1 = factory.getIgoChessman("b");  
        black2 = factory.getIgoChessman("b");  
        black3 = factory.getIgoChessman("b");  
        System.out.println("判断两颗黑子是否相同:" + (black1==black2));  

        //通过享元工厂获取两颗白子  
        white1 = factory.getIgoChessman("w");  
        white2 = factory.getIgoChessman("w");  
        System.out.println("判断两颗白子是否相同:" + (white1==white2));  

        //显示棋子,同时设置棋子的坐标位置  
        black1.display(new Coordinates(1,2));  
        black2.display(new Coordinates(3,4));  
        black3.display(new Coordinates(1,3));  
        white1.display(new Coordinates(2,5));  
        white2.display(new Coordinates(2,4));  
    }  
}  

14.3 完整化解方案

     
 为了节省存储空间,提升系统个性,Sunny集团开发人士使用享元形式来设计围棋软件中的棋子,其主导结构如图14-4所示:

图片 10

图14-4 围棋棋子结构图

      
在图14-4中,IgoChessman充当抽象享元类,BlackIgoChessman和惠特eIgoChessman充当具体享元类,IgoChessmanFactory充当享元工厂类。完整代码如下所示:

import java.util.*;  

//围棋棋子类:抽象享元类  
abstract class IgoChessman {  
    public abstract String getColor();  

    public void display() {  
        System.out.println("棋子颜色:" + this.getColor());    
    }  
}  

//黑色棋子类:具体享元类  
class BlackIgoChessman extends IgoChessman {  
    public String getColor() {  
        return "黑色";  
    }     
}  

//白色棋子类:具体享元类  
class WhiteIgoChessman extends IgoChessman {  
    public String getColor() {  
        return "白色";  
    }  
}  

//围棋棋子工厂类:享元工厂类,使用单例模式进行设计  
class IgoChessmanFactory {  
    private static IgoChessmanFactory instance = new IgoChessmanFactory();  
    private static Hashtable ht; //使用Hashtable来存储享元对象,充当享元池  

    private IgoChessmanFactory() {  
        ht = new Hashtable();  
        IgoChessman black,white;  
        black = new BlackIgoChessman();  
        ht.put("b",black);  
        white = new WhiteIgoChessman();  
        ht.put("w",white);  
    }  

    //返回享元工厂类的唯一实例  
    public static IgoChessmanFactory getInstance() {  
        return instance;  
    }  

    //通过key来获取存储在Hashtable中的享元对象  
    public static IgoChessman getIgoChessman(String color) {  
        return (IgoChessman)ht.get(color);    
    }  
}  

编纂如下客户端测试代码:

class Client {  
    public static void main(String args[]) {  
        IgoChessman black1,black2,black3,white1,white2;  
        IgoChessmanFactory factory;  

        //获取享元工厂对象  
        factory = IgoChessmanFactory.getInstance();  

        //通过享元工厂获取三颗黑子  
        black1 = factory.getIgoChessman("b");  
        black2 = factory.getIgoChessman("b");  
        black3 = factory.getIgoChessman("b");  
        System.out.println("判断两颗黑子是否相同:" + (black1==black2));  

        //通过享元工厂获取两颗白子  
        white1 = factory.getIgoChessman("w");  
        white2 = factory.getIgoChessman("w");  
        System.out.println("判断两颗白子是否相同:" + (white1==white2));  

        //显示棋子  
        black1.display();  
        black2.display();  
        black3.display();  
        white1.display();  
        white2.display();  
    }  
}  

 编译并运营程序,输出结果如下:

判断两颗黑子是否相同:true

判断两颗白子是否相同:true

棋子颜色:黑色

棋子颜色:黑色

棋子颜色:黑色

棋子颜色:白色

棋子颜色:白色

      
从出口结果能够观望,即便大家拿到了八个黑子对象和八个白子对象,可是它们的内存地址相同,也等于说,它们其实是同一个目的。在贯彻享元工厂类时我们运用了单例方式和回顾工厂方式,确保了享元工厂对象的唯一性,并提供工厂方法来向客户端重回享元对象。

得以达成目的的复用——享元格局(三)

14.3 完整化解方案

为了节省存储空间,提高系统品质,Sunny公司开发职员使用享元格局来统筹围棋软件中的棋子,其主导组织如图14-4所示:

图片 11

图14-4 围棋棋子结构图

在图14-4中,IgoChessman充当抽象享元类,BlackIgoChessman和惠特eIgoChessman充当具体享元类,IgoChessmanFactory充当享元工厂类。完整代码如下所示:

import java.util.*;  

//围棋棋子类:抽象享元类  
abstract class IgoChessman {  
    public abstract String getColor();  

    public void display() {  
        System.out.println("棋子颜色:" + this.getColor());    
    }  
}  

//黑色棋子类:具体享元类  
class BlackIgoChessman extends IgoChessman {  
    public String getColor() {  
        return "黑色";  
    }     
}  

//白色棋子类:具体享元类  
class WhiteIgoChessman extends IgoChessman {  
    public String getColor() {  
        return "白色";  
    }  
}  

//围棋棋子工厂类:享元工厂类,使用单例模式进行设计  
class IgoChessmanFactory {  
    private static IgoChessmanFactory instance = new IgoChessmanFactory();  
    private static Hashtable ht; //使用Hashtable来存储享元对象,充当享元池  

    private IgoChessmanFactory() {  
        ht = new Hashtable();  
        IgoChessman black,white;  
        black = new BlackIgoChessman();  
        ht.put("b",black);  
        white = new WhiteIgoChessman();  
        ht.put("w",white);  
    }  

    //返回享元工厂类的唯一实例  
    public static IgoChessmanFactory getInstance() {  
        return instance;  
    }  

    //通过key来获取存储在Hashtable中的享元对象  
    public static IgoChessman getIgoChessman(String color) {  
        return (IgoChessman)ht.get(color);    
    }  
}

编纂如下客户端测试代码:

class Client {  
    public static void main(String args[]) {  
        IgoChessman black1,black2,black3,white1,white2;  
        IgoChessmanFactory factory;  

        //获取享元工厂对象  
        factory = IgoChessmanFactory.getInstance();  

        //通过享元工厂获取三颗黑子  
        black1 = factory.getIgoChessman("b");  
        black2 = factory.getIgoChessman("b");  
        black3 = factory.getIgoChessman("b");  
        System.out.println("判断两颗黑子是否相同:" + (black1==black2));  

        //通过享元工厂获取两颗白子  
        white1 = factory.getIgoChessman("w");  
        white2 = factory.getIgoChessman("w");  
        System.out.println("判断两颗白子是否相同:" + (white1==white2));  

        //显示棋子  
        black1.display();  
        black2.display();  
        black3.display();  
        white1.display();  
        white2.display();  
    }  
}

编译并运营程序,输出结果如下:

判断两颗黑子是否相同:true
判断两颗白子是否相同:true
棋子颜色:黑色
棋子颜色:黑色
棋子颜色:黑色
棋子颜色:白色
棋子颜色:白色

从出口结果可以观看,纵然大家取得了五个黑子对象和五个白子对象,然则它们的内存地址相同,相当于说,它们其实是同1个对象。在完成享元工厂类时大家采取了单例格局和归纳工厂形式,确保了享元工厂对象的唯一性,并提供工厂方法来向客户端重回享元对象。

单纯享元情势

  在仅仅享元格局中,全数的有血有肉享元类都以可以共享的,不存在非共享具体享元类。

图片 12

14.5 带外部状态的消除方案

      
Sunny软件商店开发人士通过对围棋棋子进行尤其分析,发现即便杏黄棋子和粉色棋子可以共享,不过它们将突显在棋盘的例外岗位,如何让同样的黑子或许白子可以数拾壹次重复展现且位于三个棋盘的两样地点?消除办法就是将棋子的地点定义为棋子的2个外表状态,在需要时再展开设置。因而,我们在图14-4中加进了三个新的类Coordinates(坐标类),用于存储每多少个棋子的地点,修改之后的构造图如图14-5所示:

图片 13

图14-5 引入外部状态之后的围棋棋子结构图

      
在图14-5中,除了扩展一个坐标类Coordinates以外,抽象享元类IgoChessman中的display()方法也将对应增添三个Coordinates类型的参数,用于在显示棋辰时指定其坐标,Coordinates类和改动今后的IgoChessman类的代码如下所示:

class Coordinates {  
    private int x;  
    private int y;  

    public Coordinates(int x,int y) {  
        this.x = x;  
        this.y = y;  
    }  

    public int getX() {  
        return this.x;  
    }  

    public void setX(int x) {  
        this.x = x;  
    }  

    public int getY() {  
        return this.y;  
    }  

    public void setY(int y) {  
        this.y = y;  
    }  
}   

//围棋棋子类:抽象享元类  
abstract class IgoChessman {  
    public abstract String getColor();  

    public void display(Coordinates coord){  
        System.out.println("棋子颜色:" + this.getColor() + ",棋子位置:" + coord.getX() + "," + coord.getY() );    
    }  
}  

 客户端测试代码修改如下:

class Client {  
    public static void main(String args[]) {  
        IgoChessman black1,black2,black3,white1,white2;  
        IgoChessmanFactory factory;  

        //获取享元工厂对象  
        factory = IgoChessmanFactory.getInstance();  

        //通过享元工厂获取三颗黑子  
        black1 = factory.getIgoChessman("b");  
        black2 = factory.getIgoChessman("b");  
        black3 = factory.getIgoChessman("b");  
        System.out.println("判断两颗黑子是否相同:" + (black1==black2));  

        //通过享元工厂获取两颗白子  
        white1 = factory.getIgoChessman("w");  
        white2 = factory.getIgoChessman("w");  
        System.out.println("判断两颗白子是否相同:" + (white1==white2));  

        //显示棋子,同时设置棋子的坐标位置  
        black1.display(new Coordinates(1,2));  
        black2.display(new Coordinates(3,4));  
        black3.display(new Coordinates(1,3));  
        white1.display(new Coordinates(2,5));  
        white2.display(new Coordinates(2,4));  
    }  
}  

编译并运转程序,输出结果如下:

判断两颗黑子是否相同:true

判断两颗白子是否相同:true

棋子颜色:黑色,棋子位置:1,2

棋子颜色:黑色,棋子位置:3,4

棋子颜色:黑色,棋子位置:1,3

棋子颜色:白色,棋子位置:2,5

棋子颜色:白色,棋子位置:2,4

      
从出口结果可以见到,在每一次调用display()方法时,都设置了差别的表面状态——坐标值,由此等同的棋子对象固然富有同等的水彩,不过它们的坐标值不相同,将展今后棋盘的差距岗位。

【作者:刘伟  http://blog.csdn.net/lovelion

 

落到实处目标的复用——享元格局(四)

14.5 带外部状态的化解方案

Sunny软件商店开发人士通过对围棋棋子进行更为分析,发现尽管玉绿棋子和反动棋子可以共享,但是它们将呈现在棋盘的分化地点,怎么样让同样的黑子或然白子可以数次重复突显且位于多个棋盘的不比地点?消除格局就是将棋子的职位定义为棋子的二个表面状态,在急需时再开展设置。由此,我们在图14-4中加进了二个新的类Coordinates(坐标类),用于存储每3个棋子的职位,修改以后的构造图如图14-5所示:

图片 14

图14-5 引入外部状态之后的围棋棋子结构图

在图14-5中,除了增添二个坐标类Coordinates以外,抽象享元类IgoChessman中的display()方法也将对应扩展2个Coordinates类型的参数,用于在显示棋龙时指定其坐标,Coordinates类和修改以往的IgoChessman类的代码如下所示:

//坐标类:外部状态类  
class Coordinates {  
    private int x;  
    private int y;  

    public Coordinates(int x,int y) {  
        this.x = x;  
        this.y = y;  
    }  

    public int getX() {  
        return this.x;  
    }  

    public void setX(int x) {  
        this.x = x;  
    }  

    public int getY() {  
        return this.y;  
    }  

    public void setY(int y) {  
        this.y = y;  
    }  
}   

//围棋棋子类:抽象享元类  
abstract class IgoChessman {  
    public abstract String getColor();  

    public void display(Coordinates coord){  
        System.out.println("棋子颜色:" + this.getColor() + ",棋子位置:" + coord.getX() + "," + coord.getY() );    
    }  
}

客户端测试代码修改如下:

class Client {  
    public static void main(String args[]) {  
        IgoChessman black1,black2,black3,white1,white2;  
        IgoChessmanFactory factory;  

        //获取享元工厂对象  
        factory = IgoChessmanFactory.getInstance();  

        //通过享元工厂获取三颗黑子  
        black1 = factory.getIgoChessman("b");  
        black2 = factory.getIgoChessman("b");  
        black3 = factory.getIgoChessman("b");  
        System.out.println("判断两颗黑子是否相同:" + (black1==black2));  

        //通过享元工厂获取两颗白子  
        white1 = factory.getIgoChessman("w");  
        white2 = factory.getIgoChessman("w");  
        System.out.println("判断两颗白子是否相同:" + (white1==white2));  

        //显示棋子,同时设置棋子的坐标位置  
        black1.display(new Coordinates(1,2));  
        black2.display(new Coordinates(3,4));  
        black3.display(new Coordinates(1,3));  
        white1.display(new Coordinates(2,5));  
        white2.display(new Coordinates(2,4));  
    }  
}

编译并运维程序,输出结果如下:

判断两颗黑子是否相同:true
判断两颗白子是否相同:true
棋子颜色:黑色,棋子位置:1,2
棋子颜色:黑色,棋子位置:3,4
棋子颜色:黑色,棋子位置:1,3
棋子颜色:白色,棋子位置:2,5
棋子颜色:白色,棋子位置:2,4

从输出结果能够看出,在历次调用display()方法时,都设置了不一致的表面状态——坐标值,由此等同的棋类对象纵然有所同样的水彩,可是它们的坐标值差距,将体未来棋盘的两样地方。

复合享元形式

  将一些单独享元对象使用组合格局加以组合,仍是可以形成复合享元对象,那样的复合享元对象自小编不可能共享,可是它们能够分解成单纯享元对象,而后人则足以共享。

图片 15

用途:假若愿意为多个里头情状不相同的享元对象设置一如既往的外表状态,可以设想接纳复合享元模式。

兑现目的的复用——享元方式(五)

14.5 单纯享元情势和复合享元形式

正规的享元形式结构图中既涵盖可以共享的切切实实享元类,也暗含不能够共享的非共享具体享元类。可是在实质上拔取进度中,大家偶尔会用到三种分外的享元方式:单纯享元格局和复合享元形式,上边将对那二种至极的享元情势举办简易的介绍:

1.只是享元情势

在仅仅享元方式中,全部的现实享元类都以足以共享的,不存在非共享具体享元类。单纯享元形式的布局如图14-6所示:

图片 16

图14-6 单纯享元格局结构图

2.复合享元格局

将一些不过享元对象使用组合形式加以组合,仍能形成复合享元对象,那样的复合享元对象自笔者不能共享,可是它们能够分解成单纯享元对象,而后者则足以共享。复合享元格局的结构如图14-7所示:

图片 17

图14-7 复合享元方式结构图

透过复合享元情势,可以确保复合享元类CompositeConcreteFlyweight中所包蕴的各样单纯享元类ConcreteFlyweight都存有同样的外表状态,而那几个只是享元的中间景观往往可以不一样。如若希望为多少个里面景观区其他享元对象设置同样的外部状态,可以设想使用复合享元格局。

14.6 关于享元形式的几点补充

1.与此外情势的联用

享元方式平时须求和其它格局一起联用,三种常见的联用方式如下:

(1)在享元格局的享元工厂类中一般提供3个静态的工厂方法用于重回享元对象,使用简便工厂格局来生成享元对象。

(2)在1个连串中,平日唯有唯一3个享元工厂,因而可以运用单例情势开展享元工厂类的筹划。

(3)享元形式可以组合组合情势形成复合享元格局,统一对八个享元对象设置外部状态。

2.享元格局与String类

JDK类库中的String类使用了享元情势,咱们透过如下代码来加以表明:

class Demo {
       public  static void main(String args[]) {
              String  str1 = "abcd";
              String  str2 = "abcd";
              String  str3 = "ab" + "cd";
              String  str4 = "ab";
              str4  += "cd";

              System.out.println(str1  == str2);
              System.out.println(str1  == str3);
              System.out.println(str1  == str4);

              str2  += "e";
              System.out.println(str1  == str2);
       }
}

在Java语言中,如果老是执行类似String
str1=”abcd”的操作时都创制三个新的字符串对象将促成内存开销很大,因而只要第两回制造了情节为”abcd”的字符串对象str1,下一遍又创设内容一致的字符串对象str2时会将它的引用指向”abcd”,不会重新分配内存空间,从而完结了”abcd”在内存中的共享。上述代码输出结果如下:

true
true
false
false

可以见见,前多个出口语句均为true,表达str一,str2、str3在内存中引用了千篇一律的目的;假诺有三个字符串str4,其初值为”ab”,再对它举行操作str4
+=
“cd”,此时虽说str4的情节与str1相同,可是出于str4的开头值差异,在创设str4时重新分配了内存,所以第多少个出口语句结果为false;最终3个输出语句结果也为false,表达当对str2举办改动时将成立3个新的靶子,修改工作在新对象上达成,而原来引用的目的并不曾爆发其余变动,str1仍旧引用原有对象,而str2引用新目的,str1与str2引用了三个精光差其余目的。

扩展

至于Java
String类那种在改动享元对象时,先将原有对象复制一份,然后在新目的上再履行修改操作的建制称为“Copy
On Write”,大家可以自行查询有关质感来越发通晓和读书“Copy On
Write”机制,在此不作详细表达。

14.7 享元格局总括

当系统中留存大气相同只怕相似的靶巳时,享元形式是一种较好的消除方案,它经过共享技术已毕平等或貌似的细粒度对象的复用,从而省去了内存空间,进步了系统特性。比较其余结构型设计情势,享元形式的接纳效用并不算太高,可是作为一种以“节约内存,升高质量”为出发点的设计方式,它在软件开发中大概获得了必然水准的利用。

1.十分紧要优点

享元格局的最首要优点如下:

(1)
能够大幅度减弱内存中对象的多少,使得同一或一般对象在内存中只保留一份,从而得以节约系统财富,提升系统脾性。

(2)
享元格局的表面状态相对独立,而且不会影响其里面意况,从而使得享元对象足以在差距的条件中被共享。

2.主要弱点

享元形式的严重性症结如下:

(1)
享元形式使得系统变得复杂,必要分离出其中景况和表面状态,这使得程序的逻辑复杂化。

(2)
为了使对象足以共享,享元格局需求将享元对象的片段情状外部化,而读取外部状态将使得运转时刻变长。

3.适用场景

在偏下情形下得以考虑采纳享元方式:

(1) 3个系统有大批量均等只怕相似的靶子,造成内存的豁达消耗。

(2) 对象的半数以上动静都可以外部化,能够将那么些外部状态传入对象中。

(3)
在接纳享元形式时索要敬重3个储存享元对象的享元池,而那要求费用一定的系统资源,由此,应当在急需多次重复使用享元对象时才值得使用享元方式。

练习

萨妮软件商店欲开发二个多职能文档编辑器,在文本文档中得以插入图片、动画、视频等多媒体资料,为了省去系统能源,相同的图片、动画和录像在同一个文档中只需保留一份,不过可以数十四次重复出现,而且它们每回出现时地方和大小均可不等。试使用享元格局设计该文档编辑器。

训练会在我的github上做掉

补充表达

  • 享元形式常常要求和其余格局一起联用,二种普遍的联用形式如下:
    (1)在享元方式的享元工厂类中司空见惯提供3个静态的厂子方法用于再次回到享元对象,使用简单工厂情势来生成享元对象。
    (2)在二个系统中,寻常唯有唯一壹个享元工厂,由此可以利用单例格局进展享元工厂类的筹划。
    (3)享元情势可以组成结缘格局变异复合享元格局,统一对三个享元对象设置外部状态。
  • JDK类库中的String类使用了享元情势

总结

器重优点:
(1)
可以极大减弱内存中对象的数目,使得一样或貌似对象在内存中只保留一份,从而可以省去系统能源,进步系统质量。
(2)
享元方式的外表状态相对独立,而且不会影响其内部景况,从而使得享元对象足以在区其他条件中被共享。
最紧要缺点:
(1)
享元格局使得系统变得复杂,需求分离出其中景况和表面状态,那使得程序的逻辑复杂化。
(2)
为了使对象足以共享,享元方式须要将享元对象的部分情形外部化,而读取外部状态将使得运营时刻变长。

适用场景

(1) 壹个系统有雅量如出一辙可能相似的靶子,造成内存的汪洋消耗。
(2) 对象的半数以上动静都可以外部化,可以将那一个外部状态传入对象中。
(3)
在使用享元情势时必要维护三个囤积享元对象的享元池,而那亟需消耗一定的系统财富,因而,应当在须要多次重复使用享元对象时才值得使用享元情势。

相关文章