享元模式
享元模式,直译“轻量模式”,它允许通过共享技术有效的支持大量细粒度的对象。在GOF95中轻量模式被分类为结构型模式。
- Flyweight:抽象享元角色,那些需要外部状态的操作通过方法参数传入,它们改变方法的行为,但是绝不影响享元的内部状态
- ConcreteFlyweight:具体享元角色,如果有内部状态,必须负责为内部状态提供存储空间,享元对象的内部状态必须和对象所处的环境无关,从而使得享元对象可以被共享
- UnsharedConcreteFlyweight:非共享的具体享元角色。Flyweight接口让共享成为可能而不是必须。UnsharedConcreteFlyweight往往是包含了多个ConcreteFlyweight的组合,由于后者已经是享元了,作为组合的前者就没必要也被共享
- FlyweightFactory:享元工厂,负责创建和管理享元角色,必须保证享元对象可以被适当的共享,当客户端调用一个需要一个享元对象时,享元工厂负责检查是否已经存在符合要求的对象,如果没有则创建之
- Client:客户端,负责维护对享元对象的引用,并自行存储所有享元的外部状态
通过将那些重复、不变的数据缓存起来并加以复用,轻量模式可以解决大量包含重复数据的细粒度对象占用内存的问题。
享元模式的关键是合理的区分内部、外部状态,并将这两类状态隔离,内部状态与外部环境无关,因而可以保留在享元的内部,减少内存占用。外部状态则通过方法参数传入,由客户端决定。
享元模式的优点:
- 减少对象数量、节约内存空间
享元模式的缺点:
- 维护共享对象需要额外开销
ExtJS中对DOM元素的处理,使用了享元模式。Fly类相当于Flyweight角色,Ext.dom.AbstractElement.fly()则充当了FlyweightFactory角色:
Ext.dom.AbstractElement.Fly与Ext.dom.Element具有一样的结构,它允许在不创建Element的前提下来使用Element的接口来操控HTMLElement。Fly类的内部状态并不是固定的,相反,每次调用Ext.fly都会导致Fly.dom属性被设置为最新的HTMLElement,这种设计是为了透明性的考虑——与Element的接口一致。
享元角色Fly通常是单例的,这是因为浏览器中的JavaScript代码不涉及线程问题,一个Fly通常就足够,只需要根据需要切换dom属性即可改变方法的行为。为了解决外部状态:dom入侵导致的共享性问题,ExtJS允许创建多个Fly的实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
fly: function(dom, named) { var fly = null, _flyweights = AbstractElement._flyweights; //静态变量存储所有享元对象的实例 //named用来指定使用哪个享元实例,这可以用来避免外部状态dom入侵享元对象导致的不可共享问题 named = named || '_global'; dom = Ext.getDom(dom); //获得HTMLElement对象 if (dom) { //如果享元不存在,则创建之 fly = _flyweights[named] || (_flyweights[named] = new AbstractElement.Fly()); fly.dom = dom; //外部状态入侵 fly.$cache = dom.id ? Ext.cache[dom.id] : null; } return fly; } |
Boolean、Byte、Character、Short、Long类的静态方法valueOf()实现了享元模式,例如:
1 2 3 4 5 6 7 8 9 |
//对于一些较小的整数,使用享元来进行共享,绝对值过大的整数则不使用享元 public class Integer { public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; else return new Integer(i); } } |
由于这些作为享元的类都实现了不变模式(Immutable) ,因此调用它们的任何方法,都不会破坏享元的可共享性
Java中的字符串也使用了享元模式,由于String类同样实现了不变模式,因此内容相同的字符串完全可以享元,这一模式已经内置到了Java的语法中:
1 2 3 4 5 |
String s1 = "123"; String s3 = "123"; //自动化的、基于语法的享元 String s2 = String.valueOf( "123" ); //本质上还是一个对象 String s4 = new String( "123" ); //这个就不是享元了,直接在堆上分配的对象 assert ( s1 == s2 && s1 == s3 ); |
- 与单例模式联用:享元工厂一般实现为单例
- 与组合模式联用:非共享的享元对象往往是共享享元对象的组合
- 与状态模式联用:在状态模式中,会存在大量细粒度的状态对象,它们基本是可共享的:它们都针对性处理某一状态;变化的部分一般由Context传入
Leave a Reply