`
lastsoul
  • 浏览: 34787 次
  • 性别: 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
分享到:
评论

相关推荐

    设计模式的享元模式的例子

    享元模式是软件设计模式中的一种结构型模式,它的主要目的是通过共享大量细粒度对象来减少内存的使用,提高系统性能。在许多场景下,尤其是处理大量相似对象时,享元模式能显著减少内存开销。这个压缩包文件...

    软件设计模式——享元模式设计报告

    享元模式是一种软件设计模式,它的主要目的是为了提高性能,减少对象的创建,尤其是在大量相似对象需要被创建的情况下。在给定的咖啡店案例中,享元模式的应用可以帮助优化内存使用,避免为每杯咖啡的配料表分配独立...

    享元模式,内含可运行代码和解释

    享元模式是一种经典的设计模式,属于结构型模式,它的核心思想是通过共享已经存在的对象来减少内存中的对象数量,从而提高系统性能。在许多场景下,特别是计算机编程中,我们可能会遇到大量的相似对象,比如在图形...

    设计模式之享元模式

    享元模式是软件设计模式中的一种结构型模式,它的主要目的是为了提高性能,尤其是在处理大量对象时。在享元模式中,通过共享技术来有效支持大量细粒度的对象,从而减少内存消耗。《设计模式之禅》这本书是设计模式...

    享元模式代码示例

    享元模式是一种结构型设计模式,它通过共享已有对象来减少内存中对象的数量,从而达到降低内存占用的目的。在面向对象编程中,当系统需要创建大量相似对象时,享元模式能够有效地提升性能,因为大部分对象是重复的,...

    享元模式Demo

    享元模式是一种优化资源使用的软件设计模式,尤其适用于对象创建成本较高或系统内存有限的情况。在计算机科学中,特别是编程领域,享元模式通过共享大量相似对象来减少内存占用,提高程序性能。它通过将对象的状态...

    设计模式学习笔记--Flyweight享元模式.docx

    享元模式是一种设计模式,属于构造型模式,其主要目的是减少对象的数量,通过共享大量相似对象的内部状态来节省内存。这种模式在处理大量细粒度对象时特别有用,能够有效地提高系统的性能。 享元模式的核心是...

    总结JavaScript设计模式编程中的享元模式使用

    享元模式是软件工程中一种用于优化性能的设计模式,它通过共享相似对象的实例来减少内存使用或者计算的开销。在JavaScript编程中,尤其是处理DOM操作和大量相似对象时,享元模式的使用尤为关键。 享元模式的主要...

    Android享元模式Demo

    享元模式是一种优化资源利用、减少对象创建的设计模式,它在Android开发中也有着广泛的应用。在这个"Android享元模式Demo"中,我们将探讨如何在Android环境中实现享元模式,并通过具体的实例来理解其工作原理。 享...

    设计模式-享元模式(讲解及其实现代码)

    享元模式是一种结构型设计模式,它通过共享已有对象来减少内存中对象的数量,从而达到降低系统内存占用、提高性能的目的。在软件工程中,当系统中存在大量相似或重复的对象时,享元模式尤为适用。 享元模式的核心是...

    Java设计模式之享元模式

    享元模式是软件设计模式中的一种结构型模式,它的核心思想是通过共享已经存在的对象来减少内存中的对象数量,从而提高系统性能。在Java中,享元模式常用于处理大量相似对象的场景,例如在图形界面中绘制大量相似的...

    第15章_享元模式.ppt

    在享元模式中可以共享的相同内容称为内部状态(Intrinsic State),而那些需要外部环境来设置的不能共享的内容称为外部状态(Extrinsic State),由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同...

    享元模式代码+文档

    享元模式是一种结构型设计模式,它通过共享已有对象来减少系统中对象的数量,从而达到降低内存占用的目的。在软件工程中,当一个系统需要处理大量相似对象时,享元模式能够有效地提升性能,尤其在处理轻量级对象时...

    享元模式flyweight

    享元模式(Flyweight Pattern)是一种结构型设计模式,它能有效地减少系统中对象的数量,从而降低内存消耗,提高性能。这种模式通过共享大量相似对象的内部状态来达到这一目标,而只保留对象的外部状态在外部管理。...

    设计模式(C#)之享元模式(Flyweight Pattern)

    享元模式是设计模式中的一种结构型模式,它主要通过共享已有对象来减少内存中对象的数量,从而提高系统性能。在C#编程中,享元模式尤其适用于那些创建大量相似对象且内存消耗较大的场景。本篇文章将深入探讨享元模式...

    享元模式的分析以及实际应用.docx

    享元模式是一种优化资源利用的设计模式,主要用于减少创建和管理对象的数量,以达到节省内存的目的。在软件工程中,尤其是在处理大量相似对象时,享元模式能够显著提高系统的性能。享元模式通过共享那些可以共享的...

    C#面向对象设计模式纵横谈(12):Flyweight 享元模式(结构型模式) (Level 300)

    享元模式是面向对象设计中的一种结构型模式,它的主要目的是通过共享大量相似对象来减少内存的使用,提高系统的性能。在C#编程语言中,我们可以利用享元模式来优化那些具有大量实例但大部分状态可以共享的对象。在这...

    java设计模式【之】享元模式【源码】【场景:多缓存可选模式】

    java设计模式【之】享元模式【源码】【场景:多缓存可选模式】 /** * 享元模式(预加载单例) * “元” 理解为 “同一地址对象” * 运用共享技术,减少对象的创建,降低对象的数量,降低内存消耗,提高性能 * ...

    享元模式1

    享元模式 享元模式(Flyweight Pattern)是一种结构型设计模式,旨在减少大量相似对象的创建,降低系统资源的开销和内存压力。该模式通过共享已经存在的对象来大幅减少需要创建的对象数量,避免创建大量相似类带来...

Global site tag (gtag.js) - Google Analytics