顿搜
享元模式(Flyweight)——23种设计模式之结构型模式
面向对象很好地解决了系统抽象性的问题,同时在大多数情况下,也不会损及系统的性能。 但在某些特殊的应用中,由于对象的数量太大, 采用面向对象会给系统带来难以承受的内存开销。比如图形应用中的图元等对象、字处理应 用中的字符对象等。
采用纯粹对象方案的问题在于大量细粒度的对 象会很快充斥在系统中,从而带来很高的运行时代价------主要指内存需求方面的代价。
如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作?
当对象的粒度太小的时候,大量对象将会产生巨大的资源消耗,因此考虑用共享对象(flyweight)来实现逻辑上的大量对象。Flyweight模式主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题。Flyweight对象可用于不同的context 中,本身固有的状态(内部状态)不随context发生变化,而其他的状态(外部状态)随context而变化
Flyweight模式对那些通常因为数量太大而难以用对象 来表示的概念或实体进行建模.
一、知识外延
相关补充
- ①、Flyweight在拳击比赛中指最轻量级,即"蝇量级",有 些翻译为"羽量级"。这里使用"享元模式"更能反映模式 的用意。
- ②、享元模式以共享的方式高效地支持大量的细粒度对象。 享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)。内蕴状态是存储 在享元对象内部并且不会随环境改变而改变。因此内 蕴状态可以共享。
- ③、外蕴状态是随环境改变而改变的、不可以共享的状态。 享元对象的外蕴状态必须由客户端保存,并在享元对 象被创建之后,在需要使用的时候再传入到享元对象 内部。外蕴状态与内蕴状态是相互独立的。
二、类图表示
享元模式涉及到Flyweight(享元角色),ConcreteFlyweight(具体享元角色),UnsharedConcreteFlyweight(复合享元角色),FlyweightFactory(享员工厂角色),Client(客户端)等五个角色。
- ①、Flyweight:描述一个接口,通过这个接口flyweight可以接受并作用于外部状态。
- ②、ConcreteFlyweight:实现Flyweight接口,并为内部状态增加存储空间。必须是可以共享的,存储的状态必须是内部的。
- ③、UnsharedConcreteFlyweight:不强制共享。
- ④、FlyweightFactory:创建并管理flyweight对象,确保合理地共享flyweight,提供已创建的flyweight实例或者创建一个(如果不存在的话)
- ⑤、Client:维持一个对flyweight的引用,计算或存储一个(多个)flyweight的外部状态
1、单纯享元模式:在单纯享元模式中,所有的享元对象 都是可以共享的。
2、复合享元模式:将一些单纯享元使用组合模式加以复合,形 成复合享元对象。这样的复合享元对象本身不能共享,但是 它们可以分解成单纯享元对象,而后者则可以共享。
三、代码示例
//抽象的Flyweight类
public abstract class Flyweight{
public abstract void operation();
}//实现一个具体类
public class ConcreteFlyweight extends Flyweight{
private String string; //内部状态作为成员属性
public ConcreteFlyweight(String str){
string = str;
}
public void operation(){
//外部状态不保存在享元对象中, 使用时由外部设置,即使是同一对象,每次调用时可传入不 同的外部状态
System.out.println("Concrete---Flyweight : " + string);
}
}//实现一个工厂方法类
public class FlyweightFactory{
private Hashtable flyweights = new Hashtable();
public FlyweightFactory(){}
public Flyweight getFlyWeight(Object obj){
Flyweight flyweight = (Flyweight) flyweights.get(obj);
if(flyweight == null){
//产生新的ConcreteFlyweight
flyweight = new ConcreteFlyweight((String)obj);
flyweights.put(obj, flyweight);
}
return flyweight;
}
public int getFlyweightSize(){
return flyweights.size();
}
}//测试
public class FlyweightPattern{
FlyweightFactory factory = new FlyweightFactory();
Flyweight fly1;
Flyweight fly2;
Flyweight fly3;
Flyweight fly4;
Flyweight fly5;
Flyweight fly6;
public FlyweightPattern(){
fly1 = factory.getFlyWeight("Google");
fly2 = factory.getFlyWeight("Qutr");
fly3 = factory.getFlyWeight("Google");
fly4 = factory.getFlyWeight("Google");
fly5 = factory.getFlyWeight("Google");
fly6 = factory.getFlyWeight("Google");
}
public void showFlyweight(){
fly1.operation();
fly2.operation();
fly3.operation();
fly4.operation();
fly5.operation();
fly6.operation();
int objSize = factory.getFlyweightSize();
System.out.println("objSize = " + objSize);
}
public static void main(String[] args){
System.out.println("The FlyWeight Pattern!");
FlyweightPattern fp = new FlyweightPattern();
fp.showFlyweight();
}
}四、优缺点分析
1、优点
- ①、它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的。
- ②、享元模式并不是一个非常常见的模式,但在某些情况 下,享元模式可以成为代码重构的强大武器。
2、缺点
- ①、享元模式使系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
- ②、享元模式将享元对象的状态外部化,而读取外部状态 使得运行时间稍微变长。
五、模式间关系
1、与组合模式的关系
- ①、与Composite模式结合起来,用共享叶节点的有向无环图实现一个逻辑上的层次结构
2、与状态模式和策略模式的关系 - ①、最好用Flyweight实现State和Strategy对象
六、应用场景
一个应用程序使用了大量的对象,完全由于使用大量的对象,造成很大的存储开销
- ①、对象的大多数状态都可变为外部状态,这些对象可以按照内蕴状态分成很多的组。
- ②、如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
- ③、应用程序不依赖于对象标识,换言之,这些对象可以是不可分辨的。
- ④、使用享元模式需要维护一个记录了系统已有的所有享元的表,而这需要耗费资源。因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。
七、总结
Flyweight模式主要解决面向对象的代价问题
- ①、Flyweight执行时所需的状态必定是内部或外部 的。内部状态存储在ConcreteFlyweight中,外部对象则由Client对象存储或计算。当用户调用 flyweight对象的操作时,将该状态传递给它。
- ②、用户不应直接对ConcreteFlyweight类进行实例 化,而只能从FlyweightFactory对象得到 ConcreteFlyweight对象,以保证对它们适当地 进行共享。
- ③、把对象的状态分开:intrinsic and extrinsic。内部状态的共享节约了大量空 间,外部状态可通过计算获得从而进一步节约空间
- ④、删除外部状态:理想情况是,外部状态可以由一个单独的对象结构计算得到,且该结构的存储要求非常小。
- ⑤、管理共享对象,客户不能直接实例化flyweight, 必须通过管理器,例如FlyweightFactory。
- ⑥、Flyweight采用对象共享的做法来降低系统中对象的个数, 从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理。
- ⑦、对象的个数太大从而导致对象内存开销加大----什么样的 数量才算大?需要我们仔细地根据具体应用情况进行评 估,而不能凭空臆断。

