首页 理论教育 享元模式:大话设计模式第十二章

享元模式:大话设计模式第十二章

时间:2023-11-03 理论教育 版权反馈
【摘要】:第十二章包子——享元模式12.1包子时间:12月29日地点:XX早餐店人物:大B,小A今天是星期日,大B和小A约好了去喝早茶。

享元模式:大话设计模式第十二章

第十二章 包子——享元模式

12.1 包子

时间:12月29日  地点:XX早餐店  人物:大B,小A

今天是星期日,大B和小A约好了去喝早茶

他们早早到早餐店里,由于这个早餐店包子是出了名的好吃。所以他们点了菜包和肉包。不一会,早餐店里坐满了人。

小A:“这里不知道有什么包子?这么受欢迎。”

大B:“这个店主要是做菜包和肉包的。”

小A:“就只有这两种口味吗?”

大B:“是啊!”

小A:“可是为什么这么受欢迎哩?这么多来这里吃包子的人都只是点菜包和肉包这两种包子吗?”

大B:“是啊!”

12.2 享元模式

大B:“你现在知道享元模式的意图了吗?”

小A:“享元模式的意图是运用共享技术有效地支持大量细粒度的对象。”

大B:“是的。也就是说在一个系统中如果有多个相同的对象,那么只共享一份就可以了,不必每个都去实例化一个对象。在Flyweight模式中,由于要产生各种各样的对象,所以在Flyweight(享元)模式中常出现Factory模式。Flyweight的内部状态是用来共享的,Flyweight factory负责维护一个对象存储池(Flyweight Pool)来存放内部状态的对象。Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度。”

如图12-1所示享元模式结构图

图12-1 享元模式结构图

12.3 享元模式原理

小A:“享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)。”

大B:“是的。一个内蕴状态是存储在享元对象内部的,并且不会随环境改变而有所不同的。因此,一个享元可以具有内蕴状态并可以共享。 一个外蕴状态是随环境改变而改变的,不可以共享状态。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传到享元对象内部。外蕴状态不可以影响享元对象的内蕴状态,它们是相互独立的。所有的内蕴状态在对象创建完后就不可再改变。”

12.4 享元模式设计初衷

小A:“师兄,享元模式的设计初衷是什么?”

大B:“面向对象语言的原则就是一切都是对象,但是如果真正使用起来,有时对象数可能显得很庞大,比如,字处理软件,如果以每个文字都作为一个对象,几千个字,对象数就是几千,无疑耗费内存,那么我们还是要‘求同存异’,找出这些对象群的共同点,设计一个元类,封装可以被共享的类,另外,还有一些特性是取决于应用(context),是不可共享的。”

12.5 咖啡外卖店

大B:“我就以咖啡外卖店写几个Java类来描述说明Flyweight设计模式的实现方式吧。”

客户买咖啡下订单,订单只区分咖啡口味,如果下了1W个订单,而咖啡店只卖20种口味的咖啡,那么我们就没有必要生成1W个订单对象,通过享元模式我们只需要生成20个订单对象。

这个例子举的不太好,但足以说明问题。下面是具体的代码。

1、 Order.java 订单抽象类

2、 FlavorOrder.java 订单实现类

3、 FlavorFactory.java 订单生成工厂

4、 Client.java 客户类、带有main方法的测试类

=============== 1、 Order.java

=============== 1 end

=============== 2、 FlavorOrder.java

=============== 2 end

=============== 3、 FlavorFactory.java

=============== 3 end

=============== 4、 Client.java

12.6 享元模式特征

大B:“你知道享元模式有哪些特征吗?”

小A:“享元模式包括有单纯享元模式和复合享元模式。他们都有不同的角色不同的特征。”

大B:“下面我来具体说说。

首先单纯享元模式,它有抽象享元角色、具体享元角色、享元工厂角、客户端角色。

抽象享元角色:所有的具体享元类的超类,规定出需要实现的公共接口。那些需要外蕴状态的操作可以通过方法的参数传入。

具体享元角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。

享元工厂角:负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象时,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。如果有,享元工厂就提供这个已经有的享元对象,如果没有,享元工厂创建一个适当的享元对象。(www.xing528.com)

客户端角色:需要维护一个对所有享元对象的引用。本角色需要自行存储所有享元对象的外蕴状态。

还有就是复合享元模式,它有抽象享元角色、具体享元角色、复合享元角色、享元工厂角、客户端角色。

抽象享元角色:所有的具体享元类的超类,规定出需要实现的公共接口。那些需要外蕴状态的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。

具体享元角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。

复合享元角色是由具体享元角色通过复合而成。复合享元角色:复合享元角色所代表的对象是不可以共享的,但是可以分解成多个可以共享的具体享元角色。

享元工厂角:负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象时,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。如果有,享元工厂就提供这个已经有的享元对象,如果没有,享元工厂创建一个适当的享元对象。

客户端角色:需要维护一个对所有享元对象的引用。本角色需要自行存储所有享元对象的外蕴状态。”

如图12-2单纯享元模式类图、图12-3复合享元模式类图

图12-2 单纯享元模式类图

图12-3 复合享元模式类图

12.7 适用性

大B:“你说一下享元模式适用于哪些地方?”

小A:“Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。当以下情况都成立时使用Flyweight模式。1、一个应用程序使用了大量的对象。2、完全由于使用大量的对象,造成很大的存储开销。3、对象的大多数状态都可变为外部状态。4、如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。5、应用程序不依赖对象标识。”

12.8 为什么使用享元模式?

小A:“为什么要使用享元模式?”

大B:“面向对象语言的原则就是一切都是对象,但是如果真正使用起来,有时对象数可能显得很庞大,比如,字处理软件,如果以每个文字都作为一个对象,几千个字,对象数就是几千,无疑耗费内存,那么我们还是要‘求同存异’,找出这些对象群的共同点,设计一个元类,封装可以被共享的类,另外,还有一些特性是取决于应用(context),是不可共享的,这也Flyweight中两个重要概念内部状态intrinsic和外部状态extrinsic之分。说白点,就是先捏一个的原始模型,然后随着不同场合和环境,再产生各具特征的具体模型,很显然,在这里需要产生不同的新对象,所以Flyweight模式中常出现Factory模式.Flyweight的内部状态是用来共享的,Flyweight factory负责维护一个Flyweight pool(模式池)来存放内部状态的对象。”

大B:“Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度.应用场合很多:比如你要从一个数据库中读取一系列字符串,这些字符串中有许多是重复的,那么我们可以将这些字符串储存在Flyweight池(pool)中。”

12.9 如何使用享元模式?

小A:“如何去使用享元模式?”

大B:“我们先从Flyweight抽象接口开始。”

大B:“接下来我们讲的是接口的具体实现(ConcreteFlyweight),并为内部状态增加内存空间, ConcreteFlyweight必须是可共享的,它保存的任何状态都必须是内部(intrinsic),也就是说,ConcreteFlyweight必须和它的应用环境场合无关。”

小A:“是不是所有的Flyweight具体实现子类都需要被共享?”

大B:“当然,并不是所有的Flyweight具体实现子类都需要被共享的,所以还有另外一种不共享的ConcreteFlyweight。”

大B:“Flyweight factory负责维护一个Flyweight池(存放内部状态),当客户端请求一个共享Flyweight时,这个factory首先搜索池中是否已经有可适用的,如果有,factory只是简单返回送出这个对象,否则,创建一个新的对象,加入到池中,再返回送出这个对象池。”

大B:“到现在为止, Flyweight模式的基本框架已经就绪,我们就来看看如何调用。”

大B:“从调用上看,好象是个纯粹的Factory使用,但奥妙就在于Factory的内部设计上。”

12.10 享元模式的优缺点

小A:“享元模式有什么优点和缺点?”

大B:“1、享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。2、享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。”

12.11 Flyweight模式在XML等数据源中应用

大B:“我们上面已经提到,当大量从数据源中读取字符串,其中肯定有重复的,那么我们使用Flyweight模式可以提高效率,以唱片CD为例,在一个XML文件中,存放了多个CD的资料。”

每个CD有三个字段:

1、出片日期(year)

2、歌唱者姓名等信息(artist)

3、唱片曲目 (title)

其中,歌唱者姓名有可能重复,也就是说,可能有同一个演唱者的多个不同时期不同曲目的CD。我们将‘歌唱者姓名’作为可共享的ConcreteFlyweight。其他两个字段作为UnsharedConcreteFlyweight。

首先看看数据源XML文件的内容:

虽然上面举例CD只有3张,CD可看成是大量重复的小类,因为其中成分只有三个字段,而且有重复的(歌唱者姓名).

CD就是类似上面接口 Flyweight:

将"歌唱者姓名"作为可共享的ConcreteFlyweight:

再看看Flyweight factory,专门用来制造上面的可共享的ConcreteFlyweight:Artist

当你有几千张甚至更多CD时,Flyweight模式将节省更多空间,共享的flyweight越多,空间节省也就越大。

给个例子,coffee商店

运行结果:

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈