`
lastsoul
  • 浏览: 33446 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

享元模式

 
阅读更多
一、引子
让我们先来复习下 java 中String 类型的特性:String 类型的对象一旦被创造就不可改
变;当两个String 对象所包含的内容相同的时候,JVM 只创建一个String 对象对应这两个
不同的对象引用。让我们来证实下着两个特性吧(如果你已经了解,请跳过直接阅读第二部
分)。
先来验证下第二个特性:
public class TestPattern {
public static void main(String[] args){
String n = "I Love Java";
String m = "I Love Java";
System.out.println(n==m);
}
}
这段代码会告诉你n==m是true,这就说明了在JVM 中n 和m两个引用了同一个String
对象。
那么接着验证下第一个特性:
在系统输出之前加入一行代码“m = m + "hehe";”,这时候n==m 结果为false,为什
么刚才两个还是引用相同的对象,现在就不是了呢?原因就是在执行后添加语句时,m 指
向了一个新创建的String 对象,而不是修改引用的对象。
呵呵,说着说着就差点跑了题,并不是每个String 的特性都跟我们今天的主题有关的。
String 类型的设计避免了在创建N 多的String 对象时产生的不必要的资源损耗,可以
说是享元模式应用的范例,那么让我们带着对享元的一点模糊的认识开始,来看看怎么在自
己的程序中正确的使用享元模式!
注:使用 String 类型请遵循《Effective Java》中的建议。
二、定义与分类
享元模式英文称为“Flyweight Pattern”,又译为羽量级模式或者蝇量级模式。我非常认
同将Flyweight Pattern 翻译为享元模式,因为这个词将这个模式使用的方式明白得表示了
出来。
享元模式的定义为:采用一个共享类来避免大量拥有相同内容的“小类”的开销。这种
开销中最常见、直观的影响就是增加了内存的损耗。享元模式以共享的方式高效的支持大量
的细粒度对象,减少其带来的开销。
在名字和定义中都体现出了共享这一个核心概念,那么怎么来实现共享呢?事物之间都
是不同的,但是又存在一定的共性,如果只有完全相同的事物才能共享,那么享元模式可以
说就是不可行的;因此我们应该尽量将事物的共性共享,而又保留它的个性。为了做到这点,
享元模式中区分了内蕴状态和外蕴状态。内蕴状态就是共性,外蕴状态就是个性了。
内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的;外蕴状态是
不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变
化是由客户端引起的)。在每个具体的环境下,客户端将外蕴状态传递给享元,从而创建不
同的对象出来。
我们引用《Java 与模式》中的分类,将享元模式分为:单纯享元模式和复合享元模式。
在下一个小节里面我们将详细的讲解这两种享元模式。
三、结构
先从简单的入手,看看单纯享元模式的结构。
1) 抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式
通过此方法传入。在Java 中可以由抽象类、接口来担当。
2) 具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供
存储空间。
3) 享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关
键!
4) 客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
来用类图来形象地表示出它们的关系吧。


再来看看复合享元模式的结构。
1) 抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式
通过此方法传入。在Java 中可以由抽象类、接口来担当。
2) 具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供
存储空间。
3) 复合享元角色:它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象
的组合。
4) 享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关
键!
5) 客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
统比一下单纯享元对象和复合享元对象,里面只多出了一个复合享元角色,但是它的结
构就发生了很大的变化。我们还是使用类图来表示下:


正如你所想,复合享元模式采用了组合模式——为了将具体享元角色和复合享元角色同
等对待和处理。这也就决定了复合享元角色中所包含的每个单纯享元都具有相同的外蕴状
态,而这些单纯享元的内蕴状态可以是不同的。
四、举例
很遗憾,没有看到享元模式实用的例子。享元模式如何来共享内蕴状态的?在能见到的
教学代码中,大概有两种实现方式:实用列表记录(或者缓存)已存在的对象和使用静态属
性。下面的例子来自于Bruce Eckel 的《Thinking in Patterns with java》一书。
设想一下有一个含有多个属性的对象,要被创建一百万次,并使用它们。这时候正是使
用享元模式的好时机:
//这便是使用了静态属性来达到共享
//它使用了数组来存放不同客户对象要求的属性值
//它相当于享元角色(抽象角色被省略了)
class ExternalizedData {
static final int size = 5000000;
static int[] id = new int[size];
static int[] i = new int[size];
static float[] f = new float[size];
static {
for(int i = 0; i < size; i++)
id[i] = i;
}
}
//这个类仅仅是为了给ExternalizedData 的静态属性赋值、取值
//这个充当享元工厂角色
class FlyPoint {
private FlyPoint() {}
public static int getI(int obnum) {
return ExternalizedData.i[obnum];
}
public static void setI(int obnum, int i) {
ExternalizedData.i[obnum] = i;
}
public static float getF(int obnum) {
return ExternalizedData.f[obnum];
}
public static void setF(int obnum, float f) {
ExternalizedData.f[obnum] = f;
}
public static String str(int obnum) {
return "id: " +
ExternalizedData.id[obnum] +
", i = " +
ExternalizedData.i[obnum] +
", f = " +
ExternalizedData.f[obnum];
}
}
//客户程序
public class FlyWeightObjects {
public static void main(String[] args) {
for(int i = 0; i < ExternalizedData.size; i++) {
FlyPoint.setI(i, FlyPoint.getI(i) + 1);
FlyPoint.setF(i, 47.0f);
}
System.out.println(
FlyPoint.str(ExternalizedData.size -1));
}
} ///:~
另外一种实现方式大概是将已存在内蕴状态不同的对象储存在一个列表当中,通过享元
工厂角色来控制重复对象的生成。而对于上面提到的复合享元模式,仅仅是在抽象享元角色
下面添加一个有组合模式来构造的复合享元角色。而且复合享元中所包含的每个单纯享元都
具有相同的外蕴状态,而这些单纯享元的内蕴状态往往是不同的。由于复合享元模式不能共
享,所以不存在什么内外状态对应的问题。所以在复合享元类中我们不用实现抽象享元对象
中的方法,因此这里采用的是透明式的合成模式。
复合享元角色仿佛没有履行享元模式存在的义务。复合享元角色是由多个具体享元角色
来组成的,虽然复合享元角色不能被共享使用,但是组成它的具体享元角色还是使用了共享
的方式。因此复合享元模式并没有违背享元模式的初衷。
五、使用优缺点
享元模式优点就在于它能够大幅度的降低内存中对象的数量;而为了做到这一步也带来
了它的缺点:它使得系统逻辑复杂化,而且在一定程度上外蕴状态影响了系统的速度。
所以一定要切记使用享元模式的条件:
1)系统中有大量的对象,他们使系统的效率降低。
2)这些对象的状态可以分离出所需要的内外两部分。
外蕴状态和内蕴状态的划分以及两者关系的对应也是非常值得重视的。只有将内外划分
妥当才能使内蕴状态发挥它应有的作用;如果划分失误,在最糟糕的情况下系统中的对象是
一个也不会减少的!两者的对应关系的维护和查找也是要花费一定的空间(当然这个比起不
使用共享对象要小得多)和时间的,可以说享元模式就是使用时间来换取空间的。可以采用
相应的算法来提高查找的速度。

另外一个例子:
package com.bankht.Flyweight;  
     
public interface Flyweight {  
    // 一个示意性方法,参数state是外蕴状态  
    public void operation(String state);  
}


具体享元角色类ConcreteFlyweight有一个内蕴状态,在本例中一个Character类型的intrinsicState属性代表,它的值应当在享元对象被创建时赋予。所有的内蕴状态在对象创建之后,就不会再改变了。
  如果一个享元对象有外蕴状态的话,所有的外部状态都必须存储在客户端,在使用享元对象时,再由客户端传入享元对象。这里只有一个外蕴状态,operation()方法的参数state就是由外部传入的外蕴状态。

package com.bankht.Flyweight;  
     
public class ConcreteFlyweight implements Flyweight {  
    private Character intrinsicState = null;  
     
    /** 
     * 构造函数,内蕴状态作为参数传入 
     *  
     * @param state 
     */
    public ConcreteFlyweight(Character state) {  
        this.intrinsicState = state;  
    }  
     
    /** 
     * 外蕴状态作为参数传入方法中,改变方法的行为, 但是并不改变对象的内蕴状态。 
     */
    @Override
    public void operation(String state) {  
        System.out.println("Intrinsic State = " + this.intrinsicState);  
        System.out.println("Extrinsic State = " + state);  
    }  
     
}


享元工厂角色类,必须指出的是,客户端不可以直接将具体享元类实例化,而必须通过一个工厂对象,利用一个factory()方法得到享元对象。一般而言,享元工厂对象在整个系统中只有一个,因此也可以使用单例模式。
  当客户端需要单纯享元对象的时候,需要调用享元工厂的factory()方法,并传入所需的单纯享元对象的内蕴状态,由工厂方法产生所需要的享元对象。

package com.bankht.Flyweight;  
     
import java.util.HashMap;  
import java.util.Map;  
     
public class FlyweightFactory {  
    private Map<Character, Flyweight> files = new HashMap<Character, Flyweight>();  
     
    public Flyweight factory(Character state) {  
        // 先从缓存中查找对象  
        Flyweight fly = files.get(state);  
        if (fly == null) {  
            // 如果对象不存在则创建一个新的Flyweight对象  
            fly = new ConcreteFlyweight(state);  
            // 把这个新的Flyweight对象添加到缓存中  
            files.put(state, fly);  
        }else{  
            System.out.println(state+"--->>状态对应对象已经存在");  
        }  
        return fly;  
    }  
}


  客户端类
package com.bankht.Flyweight;  
public class Client {  
     
    public static void main(String[] args) {  
     
        FlyweightFactory factory = new FlyweightFactory();  
        Flyweight fly = factory.factory(new Character('a'));  
        fly.operation("First Call");  
     
        fly = factory.factory(new Character('b'));  
        fly.operation("Second Call");  
     
        fly = factory.factory(new Character('a'));  
        fly.operation("Third Call");  
    }  
     
}
  • 大小: 13 KB
  • 大小: 23.6 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics