享元模式 FlyWeight 结构型 设计模式(十五)

享元模式(FlyWeight) 
”取“共享”之意,“”取“单元”之意。
image_5c087c0a_b27

意图

运用共享技术,有效的支持大量细粒度的对象。

意图解析

面向对象的程序设计中,一切皆是对象,这也就意味着系统的运行将会依赖大量的对象。
试想,若是对象的数量过多,势必会增长系统负担,致使运行的代价太高。
下面看两个小例子理解下
1.)有一首歌曲叫作《大舌头》
其中有一句歌词“说说说说 说你爱我 我我我我 说不出口”
image_5c087c0a_3c7e
若是使用面向对象的编程方式对这段歌词进行描述,假设一个汉字表示一个对象,你会怎么作?
你会用七个仍是十六个对象进行表示?
image_5c087c0a_1241
 
2.)有一个文本编辑器软件,对于每个字符使用对象进行表示
当打开一篇有不少重复字符的、数万字的文章时,你会使用几个对象进行表示?
若是仍旧采用每一个字符占用一个对象,系统势必崩溃,必然须要共享对象
 
上面的两个例子中,都涉及到重复对象的概念 
而享元模式的意图就是如此,将 重复的对象进行共享以达到支持大量细粒度对象的目的
若是不进行共享,如例2中描述的那样,一篇数万字符的文章将会产生数万个对象,这将是一场可怕的灾难。
image_5c087c0a_7394
flyweight意为轻量级
在咱们当前的场景下,寓意为经过共享技术,轻量级的---也就是内存占用更小
本质就是“共享”因此中文翻译过来多称之为享元
简言之,享元模式就是要“共享对象”
对于Java语言,咱们熟悉的String,就是享元模式的运用
String是不可变对象,一旦建立,将不会改变
在JVM内部,String对象都是共享的
若是一个系统中的两个String对象,包含的字符串相同,只会建立一个String对象提供给两个引用
从而实现String对象的共享(new 的对象是两个不一样的)
image_5c087c0a_149c
享元模式又不只仅是简单的“共享对象”
上面的两个小例子中,对于文字中的重复字符
能够经过共享对象的方式,对某些对象进行共享,从而减小内存开销。
考虑下图中的情景,这里面全部的“你”字,究竟是不是一样的?
  • 是,由于所有都是汉字“你”
  • 不是,由于尽管都是汉字“你”,可是他们的字体,颜色,字号,却又明显不一样,因此不是一样的
image_5c087c0b_388b
 
若是将字体、颜色、字号,做为“你”这个汉字的状态
是否是能够认为:他们都是同样的汉字,可是他们却又具备不一样的状态?
其实享元模式不只仅用来解决大量重复对象的共享问题,还可以用来解决类似对象的问题。
享元对象可以共享的关键在于:区分对象的 内部状态外部状态
内部状态是存储在享元对象内部的,而且不会随环境的变化而有所改变。
好比上面的汉字“你”,不管在任何状况下,汉字“你”,始终是“你”,不会变成“她”
因此说享元模式解决共享问题,本质是共享内部状态
外部状态是随外部环境变化而变化,不能共享的状态。
享元对象的外部状态一般由客户端保存,在必要的时候在传递到享元对象内部
好比上面汉字“你”的字体、颜色、字号就是外部状态。   

小结

享元模式就是为了不系统中出现大量相同或类似的对象,同时又不影响客户端程序经过面向对象的方式对这些对象进行操做
享元模式经过共享技术,实现相同或类似对象的重用
好比文编编辑器读取文本
在逻辑上每个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象
在享元模式中,存储这些共享实例对象的地方一般叫作享元池(Flyweight  Pool)
享元模式能够结合String的 intern()方法一块儿进行理解
 
经过区分了内部状态和外部状态,就能够将相同内部状态的对象存储在池中,池中的对象能够实现共享
须要的时候将对象从池中取出,实现对象的复用
经过向取出的对象注入不一样的外部状态,进而获得一些列类似的对象
而这些看似各异的对象在内存中,仅仅存储了一份,大大节省了空间,因此说很天然的命名为“flyweight”轻量级

享元工厂

经过对意图的认识,能够认为, 享元模式其实就是对于“程序中会出现的大量重复或者类似对象”的一种“重构”
固然,你应该是在设计之初就想到这个问题,而不是真的出现问题后再去真的重构
好比,你想要设计“字符”这种对象时,就应该考虑到他的“大量””重复““类似”的特色
因此须要分析出字符的内部状态,与外部状态
上面也提到对于享元对象,经过享元池进行管理
对于池的管理一般使用工厂模式,借助于工厂类对享元池进行管理
用户须要对象时,经过工厂类获取
工厂提供一个存储在享元池中的已建立的对象实例,或者建立一个新的实例

示例代码

针对于上面的例子,汉字“你”做为内部状态,能够进行共享
“颜色”做为外部状态,由客户端保存传递
建立字符类 Character、汉字字符类ChineseCharacter、颜色类Color以及工厂类CharacterFactory
Color含有颜色属性,经过构造方法设置,getter方法获取
package flyweight;
public class Color {
    public String Color;
    public Color(String color) {
        this.Color = color;
    }
    public String getColor() {
        return Color;
    }
}

 

Character 抽象的字符类,用于描述字符
package flyweight;
public abstract class Character {
    public abstract String getValue();
     
    public void display(Color color) {
        System.out.println("字符: " + getValue() + " ,颜色: " + color.getColor());
    }
}
汉字字符类,为了简化,直接设置value为汉字“你”

 

package flyweight;
public class ChineseCharacter extends Character {
    @Override
    public String getValue() {
        return "你";
    }
}
CharacterFactory字符工厂类
经过单例模式建立工厂
内部HashMap用于存储字符,而且提供获取方法
为了简化程序,初始就建立了一个汉字字符“你”存储于字符中
package flyweight;
import java.util.HashMap;
public class CharacterFactory {
    /**
    * 单例模式 饿汉式建立
    */
    private static CharacterFactory instance = new CharacterFactory();
    /**
    * 使用HashMap管理享元池
    */
    private HashMap<String, Object> hm = new HashMap<>();
    private CharacterFactory() {
        Character character = new ChineseCharacter();
        hm.put("你", character);
    }
    /**
    * 单例全局访问接口获取工厂
    */
    public static CharacterFactory getInstance() {
        return instance;
    }
     
    /**
    * 根据key获取池中的对象
    */
    public Character getCharacter(String key) {
        return (Character) hm.get(key);
    }
}
测试代码
image_5c087c0b_6037
示例中,咱们经过工厂,从享元池中获取了三个汉字字符“你”。
经过 == 能够看得出来,他们都是同一个对象
在分别调用他们的display方法时,在客户端(此处为咱们的Test main方法)中建立,而且传递给享元对象
经过方法参数的形式进行外部状态的设置。
image_5c087c0b_2137
 
CharacterFactory 单例模式,返回自身实例
CharacterFactory内部维护Character的享元池
Character 依赖Color
ChineseCharacter是Character的实现类

结构

将上面的示例转换为标准的享元模式的名称
image_5c087c0b_1edf
 
抽象享元角色 FlyWeight
全部具体享元类的超类,为这些类规定了须要实现的公共接口
外部状态能够经过业务逻辑方法的参数形式传递进来
具体享元角色ConcreteFlyWeight
实现抽象享元角色所规定的的接口
须要保存内部状态,并且,内部状态必须与外部状态无关
从而才能使享元对象能够在系统内共享
享元工厂角色 FlyWeightFactory
负责建立和管理享元角色,也就是维护享元池
必须保证享元对象能够被系统适当的共享
接受客户端的请求
若是有适当符合要求的享元对象,则返回
若是没有一个适当的享元对象,则建立
客户端角色Client
客户端角色维护了对全部享元对象的引用
image_5c087c0b_69dd
须要保存维护享元对象的外部状态,而后经过享元对象的业务逻辑方法做为参数形式传递
image_5c087c0b_6b32

分类

单纯享元模式

在上面的结构中,若是全部的ConcreteFlyWeight均可以被共享
也就是全部的FlyWeight子类均可以被共享,那就是全部的享元对象均可以被共享
这种形式被称之为 单纯享元模式
单纯享元代码
package flyweight.simple;
public abstract class FlyWeight {
/**
* 抽象的业务逻辑方法,接受外部状态做为参数
*/
abstract public void operation(String outerState);
}
package flyweight.simple;
public class ConcreteFlyWeight extends FlyWeight {
    private String innerState = null; 
    public ConcreteFlyWeight(String innerState) {
        this.innerState = innerState;
    } 
    /**
    * 外部状态做为参数传递
    */
    @Override
    public void operation(String outerState) {
        System.out.println("innerState = " + innerState + " outerState = " + outerState);
    }
}
package flyweight.simple;
import java.util.HashMap;
public class FlyWeightFactory {
    /**
    * 单例模式 饿汉式建立
    */
    private static FlyWeightFactory instance = new FlyWeightFactory();
    /**
    * 使用HashMap管理享元池
    */
    private HashMap<String, Object> hm = new HashMap<>();
     
    private FlyWeightFactory() {
    }
    /**
    * 单例全局访问接口获取工厂
    */
    public static FlyWeightFactory getInstance() {
    return instance;
    }
    /**
    * 根据innerState获取池中的对象
    * 存在返回,不存在建立并返回
    */
    public FlyWeight getFylWeight(String innerState) {
        if(hm.containsKey(innerState)){
        return (FlyWeight) hm.get(innerState);
        }else{
        FlyWeight flyWeight = new ConcreteFlyWeight(innerState);
        hm.put(innerState,flyWeight);
        return flyWeight;
        }
    }
}

 

package flyweight.simple;
public class Test {
public static void main(String[] args){
FlyWeightFactory flyWeightFactory = FlyWeightFactory.getInstance();
FlyWeight flyWeight1 = flyWeightFactory.getFylWeight("First");
FlyWeight flyWeight2 = flyWeightFactory.getFylWeight("Second");
FlyWeight flyWeight3 = flyWeightFactory.getFylWeight("First");

System.out.println(flyWeight1);
System.out.println(flyWeight2);
System.out.println(flyWeight3);
System.out.println();

flyWeight1.operation("outer state XXX");
flyWeight2.operation("outer state YYY");
flyWeight3.operation("outer state ZZZ");
}
}

 

 
image_5c087c0b_75b6   

复合享元模式

与单纯享元模式对应的是复合享元模式
单纯享元模式中,全部的享元对象均可以共享
复合享元模式中,则并非全部的ConcreteFlyWeight均可以被共享
也就是说: 不是全部的享元对象均可以被共享
实际上,并非全部的FlyWeight子类都须要被共享
FlyWeight接口使的能够进行共享,可是没有任何须要 强制必须共享
实践中,UnsharedConcreteFlyWeight对象一般将ConcreteFlyWeight对象做为子节点
image_5c087c0b_4a4f
与单纯享元模式相比,仅仅是拥有了不可共享的具体子类
并且,这个子类每每是应用了组合模式,将ConcreteFlyWeight对象做为子节点
复合享元角色UnsharedConcreteFlyWeight
复合享元角色,也就是不可共享的,也被称为 不可共享的享元对象
可是一个复合享元对象能够分解为多个自己是单纯享元对象的组合
这些单纯的享元对象就又是能够共享的
 
复合享元代码
将简单模式中的示例代码进行改造
FlyWeight不变
package flyweight.composite;
public abstract class FlyWeight {
/**
* 抽象的业务逻辑方法,接受外部状态做为参数
*/
abstract public void operation(String outerState);
}
ConcreteFlyWeight不变
package flyweight.composite;
public class ConcreteFlyWeight extends FlyWeight {
private String innerState = null;
public ConcreteFlyWeight(String innerState) {
this.innerState = innerState;
}
/**
* 外部状态做为参数传递
*/
@Override
public void operation(String outerState) {
System.out.println("innerState = " + innerState + " outerState = " + outerState);
}
}
新增长不共享的子类也就是组合的享元子类
内部使用list 维护单纯享元模式对象,提供add方法进行添加
提供operation操做
package flyweight.composite;
import java.util.ArrayList;
import java.util.List;
public class UnsharedConcreateFlyWeight extends FlyWeight {
private String innerState = null;
public UnsharedConcreateFlyWeight(String innerState) {
this.innerState = innerState;
}

private List<FlyWeight> list = new ArrayList<>();
public void add(FlyWeight flyWeight) {
list.add(flyWeight);
}
@Override
public void operation(String outerState) {
for (FlyWeight flyWeight:list) {
flyWeight.operation(outerState);
    }
}
}
FlyWeightFactory工厂类进行改造
新增长public UnsharedConcreateFlyWeight getCompositeFylWeight(String state) 
用于得到组合享元对象
package flyweight.composite;
 
import java.util.HashMap;
 
public class FlyWeightFactory {
    /**
    * 单例模式 饿汉式建立
    */
    private static FlyWeightFactory instance = new FlyWeightFactory();
     
    /**
    * 使用HashMap管理享元池
    */
    private HashMap<String, Object> hm = new HashMap<>();
     
    /**
    * 管理复合享元对象
    */
    private HashMap<String, Object> compositeHm = new HashMap<>();
     
    private FlyWeightFactory() {
    }
     
    /**
    * 单例全局访问接口获取工厂
    */
    public static FlyWeightFactory getInstance() {
        return instance;
    }
     
    /**
    * 根据innerState获取池中的对象
    * 存在返回,不存在建立并返回
    */
    public FlyWeight getFylWeight(String innerState) {
        if(hm.containsKey(innerState)){
            return (FlyWeight) hm.get(innerState);
        }else{
            FlyWeight flyWeight = new ConcreteFlyWeight(innerState);
            hm.put(innerState,flyWeight);
            return flyWeight;
        }
    }
     
    /**
    * 根据innerState获取池中的对象
    * 存在返回,不存在建立并返回
    */
    public UnsharedConcreateFlyWeight getCompositeFylWeight(String state) {
        if(compositeHm.containsKey(state)){
            return (UnsharedConcreateFlyWeight) compositeHm.get(state);
        }else{
            UnsharedConcreateFlyWeight flyWeight = new UnsharedConcreateFlyWeight(state);
            compositeHm.put(state,flyWeight);
            return flyWeight;
        }
    }
 
}
测试类也进行改造
package flyweight.composite;
public class Test {
public static void main(String[] args){
    FlyWeightFactory flyWeightFactory = FlyWeightFactory.getInstance();
    FlyWeight flyWeight1 = flyWeightFactory.getFylWeight("First");
    FlyWeight flyWeight2 = flyWeightFactory.getFylWeight("Second");
    FlyWeight flyWeight3 = flyWeightFactory.getFylWeight("First");
     
    System.out.println(flyWeight1);
    System.out.println(flyWeight2);
    System.out.println(flyWeight3);
     
    System.out.println("###########################################");
     
    flyWeight1.operation("outer state XXX");
    flyWeight2.operation("outer state YYY");
    flyWeight3.operation("outer state ZZZ");
    System.out.println("###########################################");
    UnsharedConcreateFlyWeight compositeFlyWeight = flyWeightFactory.getCompositeFylWeight("composite");
    compositeFlyWeight.add(flyWeight1);
    compositeFlyWeight.add(flyWeight2);
    compositeFlyWeight.operation("composite out state OOO");
    }
}
image_5c087c0b_5108
 
测试程序在原来的基础上,新得到了一个组合享元对象
而后将两个单纯享元对象添加到组合享元对象中
而后调用operation,经过打印信息能够看得出来
不一样的单纯享元对象,他们却有了一致的外部状态
image_5c087c0b_4c7c
 
因此使用复合享元模式的一个经常使用目的就是:
多个内部状态不一样的单纯享元对象,拥有一致的外部状态
这种场景下,就能够考虑使用复合享元模式

使用场景

若是有下列状况,则能够考虑使用享元模式
  • 应用程序中使用了大量的对象
  • 大量的对象明显增长了程序的存储运行开销
  • 对象能够提取出内部状态,而且能够分离外部状态
使用享元模式有一点须要特别注意:应用程序运行不依赖这些对象的身份
换句话说这些对象是不作区分的,适用于“在客户端眼里,他们都是同样的”这种场景
好比单纯的使用对象的方法,而不在乎对象是不是建立而来的,不然若是客户端鉴别对象的身份(equals),当他们是同一个对象时将会出现问题  

总结

享元模式的核心就是共享
共享就须要找准内部状态,以及分离外部状态外部状态由客户端维护,在必要时候,经过参数的形式注入到享元对象中
在有大量重复或者类似对象的场景下,均可以考虑到享元模式
 
并且为了达到共享的目的,须要经过 工厂对象进行控制
只有经过工厂来维护享元池才能达到共享的目的,若是任意建立使用则势必不能很好地共享
 
享元模式大大的减小了对象的建立,下降了系统所须要的内存空间
可是因为 将状态分为内部状态和外部状态,而外部状态是分离的,那么状态的读取必然会增大开销
因此说 享元模式是时间换空间
 
若是肯定须要使用享元模式,若是对于多个内部状态不一样的享元对象,但愿他们拥有一致性的外部状态
那么就能够考虑复合享元模式,复合享元模式是与合成模式的结合。
 
相关文章
相关标签/搜索