享元模式.docx
《享元模式.docx》由会员分享,可在线阅读,更多相关《享元模式.docx(21页珍藏版)》请在冰豆网上搜索。
享元模式
php
/**
*享元模式
*
采用一个共享来避免大量拥有相同内容对象的开销。
这种开销中最常见、直观的就是内存的损耗。
享元模式以共享的方式高效的支持大量的细粒度对象。
在名字和定义中都体现出了共享这一个核心概念,那么怎么来实现共享呢?
要知道每个事物都是不同的,但是又有一定的共性,如果只有完全相同的事物才能共享,那么享元模式可以说就是不可行的;因此我们应该尽量将事物的共性共享,而又保留它的个性。
为了做到这点,享元模式中区分了内蕴状态和外蕴状态。
内蕴状态就是共性,外蕴状态就是个性了。
注:
共享的对象必须是不可变的,不然一变则全变(如果有这种需求除外)。
内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的;外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。
在每个具体的环境下,客户端将外蕴状态传递给享元,从而创建不同的对象出来。
*/
classCD//共享的部分
{
private$_title=null;
private$_artist=null;
publicfunctionsetTitle($title)
{
$this->_title=$title;
}
publicfunctiongetTitle()
{
return$this->_title;
}
publicfunctionsetArtist($artist)
{
$this->_artist=$artist;
}
publicfunctiongetArtist($artist)
{
return$this->_artist;
}
}
classArtist
{
private$_name;
publicfunction__construct($name)
{
echo"construct".$name."
";
$this->_name=$name;
}
publicfunctiongetName()
{
return$this->_name;
}
}
classArtistFactory
{
private$_artists=array();
publicfunctiongetArtist($name)
{
if(isset($this->_artists[$name]))
{
return$this->_artists[$name];
}else{
$objArtist=newArtist($name);
$this->_artists[$name]=$objArtist;
return$objArtist;
}
}
}
$objArtistFactory=newArtistFactory();
$objCD1=newCD();
$objCD1->setTitle("title1");
$objCD1->setArtist($objArtistFactory->getArtist('artist1'));
$objCD2=newCD();
$objCD2->setTitle("title2");
$objCD2->setArtist($objArtistFactory->getArtist('artist2'));
$objCD3=newCD();
$objCD3->setTitle("title3");
$objCD3->setArtist($objArtistFactory->getArtist('artist1'));
享元模式英文称为“FlyweightPattern”,我非常感谢将FlyweightPattern翻译成享元模式的那位强人,因为这个词将这个模式使用的方式明白得表示了出来;如果翻译成为羽量级模式或者蝇量级模式等等,虽然可以含蓄的表现出使用此模式达到的目的,但是还是没有抓住此模式的关键。
享元模式的定义为:
采用一个共享来避免大量拥有相同内容对象的开销。
这种开销中最常见、直观的就是内存的损耗。
享元模式以共享的方式高效的支持大量的细粒度对象。
在名字和定义中都体现出了共享这一个核心概念,那么怎么来实现共享呢?
要知道每个事物都是不同的,但是又有一定的共性,如果只有完全相同的事物才能共享,那么享元模式可以说就是不可行的;因此我们应该尽量将事物的共性共享,而又保留它的个性。
为了做到这点,享元模式中区分了内蕴状态和外蕴状态。
内蕴状态就是共性,外蕴状态就是个性了。
注:
共享的对象必须是不可变的,不然一变则全变(如果有这种需求除外)。
内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的;外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。
在每个具体的环境下,客户端将外蕴状态传递给享元,从而创建不同的对象出来。
至于怎样来维护客户端保持的外蕴状态和享元内部保持的内蕴状态的对应关系,你先不用担心这个问题,我们后面会涉及到的。
我们引用《Java与模式》中的分类,将享元模式分为:
单纯享元模式和复合享元模式。
在下一个小节里面我们将详细的讲解这两种享元模式。
三、结构
先从简单的入手,看看单纯享元模式的结构。
1)抽象享元角色:
为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。
在Java中可以由抽象类、接口来担当。
2)具体享元角色:
实现抽象角色规定的方法。
如果存在内蕴状态,就负责为内蕴状态提供存储空间。
3)享元工厂角色:
负责创建和管理享元角色。
要想达到共享的目的,这个角色的实现是关键!
4)客户端角色:
维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
来用类图来形象地表示出它们的关系吧(对类图的了解可以参看我关于类图的blog)。
怎么咋看咋像简单工厂模式呢!
没错,可以说结构型的单纯享元模式和创建型的简单工厂模式实现上非常相似,但是它的重点或者用意却和工厂模式截然不同。
工厂模式的使用主要是为了使系统不依赖于实现得细节(见《深入浅出工厂模式》);而在享元模式的主要目的如前面所述:
采用共享技术来避免大量拥有相同内容对象的开销。
正所谓“旧瓶装新酒”阿!
再来看看复合享元模式的结构。
1)抽象享元角色:
为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。
在Java中可以由抽象类、接口来担当。
2)具体享元角色:
实现抽象角色规定的方法。
如果存在内蕴状态,就负责为内蕴状态提供存储空间。
3)复合享元角色:
它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。
4)享元工厂角色:
负责创建和管理享元角色。
要想达到共享的目的,这个角色的实现是关键!
5)客户端角色:
维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
统比一下单纯享元对象和复合享元对象,里面只多出了一个复合享元角色,但是它的结构就发生了很大的变化。
我们还是使用类图来表示下:
你也许又纳闷了,这个也似曾相逢!
单看左半部,和简单工厂模式类似;再看右半部,怎么这么像合成模式呢(请参看关于合成模式的文章或者期待我的《深入浅出合成模式》)!
合成模式用在此处就是为了将具体享元角色和复合享元角色同等对待和处理,通过将享元模式与合成模式组合在一起,可以确保复合享元中所包含的每个单纯享元都具有相同的外蕴状态,而这些单纯享元的内蕴状态往往是不同的。
四、举例
这里就以去餐馆吃饭为例详细的说明下享元模式的使用方式。
去菜馆点菜吃饭的过程大家一定都是轻车熟路了,这里就不赘述。
在例子中我使用了一个list来存放外蕴状态和内蕴状态的对应关系,而且提供了查询每个客人点菜情况的方法。
内蕴状态在这里代表了菜肴的种类,而外蕴状态就是每盘菜肴的点菜人。
A让我们先来看看单纯享元模式的实现吧。
先看下抽象享元角色的定义:
interfaceMenu
{
//规定了实现类必须实现设置内外关系的方法
publicvoidsetPersonMenu(Stringperson,Listlist);
//规定了实现类必须实现查找外蕴状态对应的内蕴状态的方法
publicListfindPersonMenu(Stringperson,Listlist);
}
这便是具体享元角色了:
classPersonMenuimplementsMenu
{
privateStringdish;
//在构造方法中给内蕴状态附值
publicPersonMenu(Stringdish){
this.dish=dish;
}
publicsynchronizedvoidsetPersonMenu(Stringperson,Listlist)
{
list.add(person);
list.add(dish);
}
publicListfindPersonMenu(Stringperson,Listlist)
{
ListdishList=newArrayList();
Iteratorit=list.iterator();
while(it.hasNext())
{
if(person.equals((String)it.next()))
dishList.add(it.next());
}
returndishList;
}
}
享元工厂角色,这可是关键所在,大家注意看!
classFlyweightFactory
{
privateMapmenuList=newHashMap();
privatestaticFlyweightFactoryfactory=newFlyweightFactory();
//这里还使用了单例模式,来使工厂对象只产生一个工厂实例
privateFlyweightFactory(){}
publicstaticFlyweightFactorygetInstance()
{
returnfactory;
}
//这就是享元模式同工厂模式的不同所在!
!
publicsynchronizedMenufactory(Stringdish)
{
//判断如果内蕴状态已经存在就不再重新生成,而是使用原来的,否则就重新生成
if(menuList.containsKey(dish))
{
return(Menu)menuList.get(dish);
}else{
Menumenu=newPersonMenu(dish);
menuList.put(dish,menu);
returnmenu;
}
}
//来验证下是不是真的少产生了对象
publicintgetNumber()
{
returnmenuList.size();
}
}
我们使用客户程序来试验下吧。
classClient
{
privatestaticFlyweightFactoryfactory;
publicstaticvoidmain(String[]args)
{
Listlist1=newArrayList();
factory=FlyweightFactory.getInstance();
Menulist=factory.factory("尖椒土豆丝");
list.setPersonMenu("ai92",list1);
list=factory.factory("红烧肉");
list.setPersonMenu("ai92",list1);
list=factory.factory("地三鲜");
list.setPersonMenu("ai92",list1);
list=factory.factory("地三鲜");
list.setPersonMenu("ai92",list1);
list=factory.factory("红焖鲤鱼");
list.setPersonMenu("ai92",list1);
list=factory.factory("红烧肉");
list.setPersonMenu("ai921",list1);
list=factory.factory("红焖鲤鱼");
list.setPersonMenu("ai921",list1);
list=factory.factory("地三鲜");
list.setPersonMenu("ai921",list1);
System.out.println(factory.getNumber());
Listlist2=list.findPersonMenu("ai921",list1);
Iteratorit=list2.iterator();
while(it.hasNext())
{
System.out.println(""+it.next());
}
}
}
这样便使用单纯享元模式实现了这些功能,但是你是不是发现一个人点了好几样菜的时候是不是使用很不方便?
而这种情况正好符合复合享元模式的使用条件:
复合享元中所包含的每个单纯享元都具有相同的外蕴状态,而这些单纯享元的内蕴状态往往是不同的。
由于复合享元模式不能共享,所以不存在什么内外状态对应的问题。
所以在复合享元类中我们不用实现抽象享元对象中的方法,因此这里采用的是透明式的合成模式。
那么下面我就使用复合享元模式在上例的基础上来实现一下。
首先要实现一个复合享元角色:
classPersonMenuMuchimplementsMenu
{
privateMapMenuList=newHashMap();
publicPersonMenuMuch(){}
//增加一个新的单纯享元对象
publicvoidadd(Stringkey,Menumenu)
{
MenuList.put(key,menu);
}
//两个无为的方法
publicsynchronizedvoidsetPersonMenu(Stringperson,Listlist)
{}
publicListfindPersonMenu(Stringperson,Listlist)
{
Listnothing=null;
returnnothing;
}
}
//在工厂方法中添加一个方法,实现重载。
publicMenufactory(String[]dish)
{
PersonMenuMuchmenu=newPersonMenuMuch();
Stringkey=null;
for(inti=0;i{
key=dish[i];
menu.add(key,this.factory(key));//调用了单纯享元角色的工厂方法
}
returnmenu;
}
也许我的例子举的不太恰当,但是基本上也能看出单纯享元模式和复合享元模式在实现上的特点,如果这个目的达到了那就忘了这个糟糕的例子吧(不要让它成了你深入理解享元模式的障碍),让我们来分析下这两种模式吧。
先从复杂度上来讲,复合享元模式显而易见是比单纯享元模式复杂的。
再从享元模式的关键——共享,来分析:
复合享元模式在共享上面是没有达到预期的效果,可以说是没有起到共享的目的。
虽然对于它内部包含的单纯享元角色来说还是能够起到共享的作用,但是复合享元角色中一个内蕴状态和对象使用了两个Map来保存,这肯定是不会节省什么空间和对象个数的。
所以我认为复合享元模式是违背享元模式初衷的。
因此我们应该尽量使用单纯享元模式。
在程序中你也许注意到,我对内蕴外蕴状态对应关系的保持是采用一个list表来做的,这仅仅是个举例,你完全可以采用各种能达到目的的方式来完成。
这一点也说明在享元模式中仅提供给我们怎么来吧一个对象的状态分开来达到共享,而对于关系的维护它是不关心的,也不是这个模式涉及的内容。
这样我就把享元模式使用一个例子详细的讲解了一下。
如果还是不太明白的话请回味下前面的定义与结构。
只有两者结合才能很好的体会到享元模式的用意。
五、使用优缺点
享元模式优点就在于它能够大幅度的降低内存中对象的数量;而为了做到这一步也带来了它的缺点:
它使得系统逻辑复杂化,而且在一定程度上外蕴状态影响了系统的速度。
所以一定要切记使用享元模式的条件:
1)系统中有大量的对象,他们使系统的效率降低。
2)这些对象的状态可以分离出所需要的内外两部分。
外蕴状态和内蕴状态的划分以及两者关系的对应也是非常值得重视的。
只有将内外划分妥当才能使内蕴状态发挥它应有的作用;如果划分失误,在最糟糕的情况下系统中的对象是一个也不会减少的!
两者的对应关系的维护和查找也是要花费一定的空间(当然这个比起不使用共享对象要小得多)和时间的,可以说享元模式就是使用时间来换取空间的。
在Gof的书中是使用了B树来进行对应关系查找优化。
享元模式的优点在于它大幅度地降低内存中对象的数量。
为了做到这一点,享元模式也付出了一定的代价:
1、享元模式为了使对象可以共享,它需要将部分状态外部化,这使得系统的逻辑变得复杂。
2、享元模式将享元对象的部分状态外部化,而读取外部状态使得运行时间会有所加长。
另外,我们还有一个比较关心的问题:
到底系统需要满足什么样的条件才能使用享元模式。
对于这个问题,我们总结了以下几条:
1、一个系统中存在着大量的细粒度对象;
2、这些细粒度对象耗费了大量的内存。
3、这些细粒度对象的状态中的大部分都可以外部化;
4、这些细粒度对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
5、软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
享元模式:
以共享的方式高效地支持大量的细粒度对象。
享元对象的状态:
1:
内蕴状态(InternalState)内蕴状态存储在享元对象内部且不会随环境改变而改变。
因此内蕴状态并可以共享。
2:
外蕴状态(ExternalState)。
外蕴状态是随环境改变而改变的、不可以共享的状态。
享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。
外蕴状态与内蕴状态是相互独立的。
享元模式的应用条件:
1:
一个系统有大量的对象。
2:
这些对象耗费大量的内存。
3:
这些对象的状态中的大部分都可以外部化。
4:
这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
5:
软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
享元模式的分类:
1:
单纯享元模式;
2:
复合享元模式。
不要为模式而模式——何时才用享元
我一直觉得,模式不要乱用,乱用模式是学习的阶段,但是一旦在工作中,我们去乱用模式,那么可能会造成很惨的后果。
那么究竟何时应该用享元模式呢?
1.系统中要有大量的对象,这才值得用享元模式。
否则你去维护一张对象表,就不值得了。
2.对象的创建是会消耗大量时间的过程,并且对象占用较大内存。
如果不是,那就让系统去创建吧。
3.在B/S的系统中,个人感觉享元的应用相对较少,Web的无状态,加之我们完全在客户端进行一系列的复杂逻辑,然后将之统一传递给Web服务器端,而不需要享元。
享元主要应用还是在C/S及Winform的本地程序上较多。
其余的,比如,关于外蕴状态和内蕴状态究竟何种应该使用享元的问题,如果不满足情况,您也根本没有办法去使用享元。
因此,我就不在这说那些蹩嘴的定义了。
享元模式的结构
享元模式采用一个共享来避免大量拥有相同内容对象的开销。
这种开销最常见、最直观的就是内存的损耗。
享元对象能做到共享的关键是区分内蕴状态(InternalState)和外蕴状态(ExternalState)。
一个内蕴状态是存储在享元对象内部的,并且是不会随环境的改变而有所不同。
因此,一个享元可以具有内蕴状态并可以共享。
一个外蕴状态是随环境的改变而改变的、不可以共享的。
享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。
外蕴状态不可以影响享元对象的内蕴状态,它们是相互独立的。
享元模式可以分成单纯享元模式和复合享元模式两种形式。
单纯享元模式
在单纯的享元模式中,所有的享元对象都是可以共享的。
单纯享元模式所涉及到的角色如下:
● 抽象享元(Flyweight)角色:
给出一个抽象接口,以规定出所有具体享元角色需要实现的方法。
● 具体享元(ConcreteFlyweight)角色:
实现抽象享元角色所规定出的接口。
如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。
● 享元工厂(FlyweightFactory)角色:
本角色负责创建和管理享元角色。
本角色必须保证享元对象可以被系统适当地共享。
当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。
如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
源代码
抽象享元角色类
publicinterfaceFlyweight{
//一个示意性方法,参数state是外蕴状态
publicvoidoperation(Stringstate);
}
具体享元角色类ConcreteFlyweight有一个内蕴状态,在本例中一个Character类型的intrinsicState属性代表,它的值应当在享元对象被创建时赋予。
所有的内蕴状态在对象创建之后,就不会再改变了。
如果一个享元对象有外蕴状态的话,所有的外部状态都必须存储在客户端,在使用享元对象时,再由客户端传入享元对象。
这里只有一个外蕴状态,operation()方法的参数state就是由外部传入的外蕴状态。
publicclassConcreteFlyweightimplementsFlyweight{
privateCharacterintrinsicState=null;
/***构造函数,内蕴状态作为参数传入
*@paramstate
*/
publicConcrete