设计模式:享元模式

享元模式  


一、引子 
    让我们先来复习下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)这些对象的状态可以分离出所需要的内外两部分。 
    外蕴状态和内蕴状态的划分以及两者关系的对应也是非常值得重视的。只有将内外划分 
妥当才能使内蕴状态发挥它应有的作用;如果划分失误,在最糟糕的情况下系统中的对象是 
一个也不会减少的!两者的对应关系的维护和查找也是要花费一定的空间(当然这个比起不 
使用共享对象要小得多)和时间的,可以说享元模式就是使用时间来换取空间的。可以采用 

相应的算法来提高查找的速度。 

下载:

http://download.csdn.net/detail/undoner/5335717

深入浅出设计模式-中文版


问题1:享元模式属于结构性模式的原因?

    结构性模式涉及如何组合类和对象以形成更大的结构(处理类或者对象组合),和类有关的结构型模式涉及如何合理地使用继承机制;和对象有关的结构型模式涉及如何合理得使用对象组合机制。特点是创建一组类的接口访问点,并在不破坏类的封装基础上,实现新功能。

    享元模式,运用共享技术有效地支持大量细粒度的对象,本质是:分离和共享,涉及的是对象的组合,分离的是对象状态中变与不变的部分,共享的是对象中不变的部分,所以属于结构型模式。

    问题2:享元模式与单例模式区别?

    享元模式与单例模式可以组合使用。享元模式中的享元工厂完全可以实现为单例,另外,享元工厂中缓存的享元对象,都是单例实例,可以看成是单例模式的一种变形控制,在享元工厂中来单例享元对象。

    享元模式是单例模式的一个延伸。享元模式通过享元工厂来控制多个对象的单例化。而单例化解决的只是本身的单例问题


原文地址:https://www.cnblogs.com/wuyida/p/6301018.html