GOF设计模式-对象结构型模式-享元模式

26 篇文章 0 订阅
20 篇文章 0 订阅

实现对象的复用-享元模式

 

在软件系统中,有时候也会存在资源浪费的情况,例如在计算 机内存中存储了多个完全相同或者非常相似的对象,如果这些对象的数量太多将导致系统运 行代价过高,内存属于计算机的“稀缺资源”,不应该用来“随便浪费”,那么是否存在一种技术 可以用于节约内存使用空间,实现对这些相同或者相似对象的共享访问呢?答案是肯定,这 种技术就是我们本章将要学习的享元模式。

举个栗子:

围棋棋子的设计

软件公司欲开发一个围棋软件,其界面效果如图:

 

软件公司开发人员通过对围棋软件进行分析,发现在围棋棋盘中包含大量的黑子和白 子,它们的形状、大小都一模一样,只是出现的位置不同而已。如果将每一个棋子都作为一 个独立的对象存储在内存中,将导致该围棋软件在运行时所需内存空间较大,如何降低运行 代价、提高系统性能是Sunny公司开发人员需要解决的一个问题。为了解决这个问题,Sunny 公司开发人员决定使用享元模式来设计该围棋软件的棋子对象,那么享元模式是如何实现节 约内存进而提高系统性能的呢?别着急,下面让我们正式进入享元模式的学习。

享元模式概述

当一个软件系统在运行时产生的对象数量太多,将导致运行代价过高,带来系统性能下降等 问题。例如在一个文本字符串中存在很多重复的字符,如果每一个字符都用一个单独的对象 来表示,将会占用较多的内存空间,那么我们如何去避免系统中出现大量相同或相似的对 象,同时又不影响客户端程序通过面向对象的方式对这些对象进行操作?享元模式正为解决 这一类问题而诞生。享元模式通过共享技术实现相同或相似对象的重用,在逻辑上每一个出 现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象,这个对象可以 出现在一个字符串的不同地方,相同的字符对象都指向同一个实例,在享元模式中,存储这 些共享实例对象的地方称为享元池(Flyweight Pool)。我们可以针对每一个不同的字符创建一个 享元对象,将其放在享元池中,需要时再从享元池取出。

 

享元模式以共享的方式高效地支持大量细粒度对象的重用,享元对象能做到共享的关键是区 分了内部状态(Intrinsic State)和外部状态(Extrinsic State)。

下面将对享元的内部状态和外部状态 进行简单的介绍:

(1) 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。 如字符的内容,不会随外部环境的变化而变化,无论在任何环境下字符“a”始终是“a”,都不会 变成“b”。

(2) 外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端 保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。一个外部状态与 另一个外部状态之间是相互独立的。如字符的颜色,可以在不同的地方有不同的颜色,例如 有的“a”是红色的,有的“a”是绿色的,字符的大小也是如此,有的“a”是五号字,有的“a”是四 号字。而且字符的颜色和大小是两个独立的外部状态,它们可以独立变化,相互之间没有影 响,客户端可以在使用时将外部状态注入享元对象中。

正因为区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中, 享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复 用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内 存中实际上只存储一份。

享元模式定义如下:

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使 用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于 享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种 对象结构型模式。

 

完整解决方案

为了节约存储空间,提高系统性能,Sunny公司开发人员使用享元模式来设计围棋软件中的棋 子,其基本结构如图

IgoChessman充当抽象享元类,BlackIgoChessman和WhiteIgoChessman充当具体享 元类,IgoChessmanFactory充当享元工厂类。完整代码如下所示:

import java.util.*;

//围棋棋子类:抽象享元类 
abstract class IgoChessman {
    public abstract String getColor();

    public void display() {
        System.out.println("棋子颜色:" + this.getColor());
    }
}

//黑色棋子类:具体享元类 
class BlackIgoChessman extends IgoChessman {
    public String getColor() {
        return "黑色";
    }
}

//白色棋子类:具体享元类 
class WhiteIgoChessman extends IgoChessman {
    public String getColor() {
        return "白色";
    }
}

//围棋棋子工厂类:享元工厂类,使用单例模式进行设计 
class IgoChessmanFactory {
    private static IgoChessmanFactory instance = new IgoChessmanFactory();
    private static Hashtable ht;

    //使用Hashtable来存储享元对象,充当享元池 
 private IgoChessmanFactory() {
        ht = new Hashtable();
        IgoChessman black, white;
        black = new BlackIgoChessman();
        ht.put("b", black);
        white = new WhiteIgoChessman();
        ht.put("w", white);
    }

    //返回享元工厂类的唯一实例 
 public static IgoChessmanFactory getInstance() {
        return instance;
    }

    //通过key来获取存储在Hashtable中的享元对象 
 public static IgoChessman getIgoChessman(String color) {
        return (IgoChessman) ht.get(color);
    }
}

//编写如下客户端测试代码:
class Client {
    public static void main(String args[]) {
        IgoChessman black1, black2, black3, white1, white2;
        IgoChessmanFactory factory;
        //获取享元工厂对象 
 factory = IgoChessmanFactory.getInstance();
        //通过享元工厂获取三颗黑子 
 black1 = factory.getIgoChessman("b");
        black2 = factory.getIgoChessman("b");
        black3 = factory.getIgoChessman("b");
        System.out.println("判断两颗黑子是否相同:" + (black1 == black2));
        //通过享元工厂获取两颗白子 
 white1 = factory.getIgoChessman("w");
        white2 = factory.getIgoChessman("w");
        System.out.println("判断两颗白子是否相同:" + (white1 == white2));
        //显示棋子 
 black1.display();
        black2.display();
        black3.display();
        white1.display();
        white2.display();
    }
}

编译并运行程序,输出结果如下:

 判断两颗黑子是否相同:true

 判断两颗白子是否相同:true

棋子颜色:黑色

棋子颜色:黑色

棋子颜色:黑色

棋子颜色:白色

棋子颜色:白色

从输出结果可以看出,虽然我们获取了三个黑子对象和两个白子对象,但是它们的内存地址 相同,也就是说,它们实际上是同一个对象。在实现享元工厂类时我们使用了单例模式和简 单工厂模式,确保了享元工厂对象的唯一性,并提供工厂方法来向客户端返回享元对象。

 

在JAVA语言中,String类型就是使用了享元模式。String对象是final类型,对象一旦创建就不可改变。在JAVA中字符串常量都是存在常量池中的,JAVA会确保一个字符串常量在常量池中只有一个拷贝。String a="abc",其中"abc"就是一个字符串常量。

熟悉java的应该知道下面这个例子:

String a = "hello";
String b = "hello";
if(a == b)
System.out.println("OK");
else
System.out.println("Error");

输出结果是:OK。可以看出if条件比较的是两a和b的地址,也可以说是内存空间

 

 

 

享元模式总结

当系统中存在大量相同或者相似的对象时,享元模式是一种较好的解决方案,它通过共享技 术实现相同或相似的细粒度对象的复用,从而节约了内存空间,提高了系统性能。相比其他 结构型设计模式,享元模式的使用频率并不算太高,但是作为一种以“节约内存,提高性能”为 出发点的设计模式,它在软件开发中还是得到了一定程度的应用。

1.主要优点

享元模式的主要优点如下:

(1) 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节 约系统资源,提高系统性能。

(2) 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同 的环境中被共享。

2.主要缺点 享元模式的主要缺点如下:

(1) 享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂 化。

(2) 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使 得运行时间变长。

3.适用场景 在以下情况下可以考虑使用享元模式:

(1) 一个系统有大量相同或者相似的对象,造成内存的大量耗费。

(2) 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

(3) 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源, 因此,应当在需要多次重复使用享元对象时才值得使用享元模式。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值