一、引子
让我们先来复习下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对象(如果你还分不清== 和equals的区别的话,请先确认)。
那么接着验证下第一个特性:
在系统输出之前加入一行代码“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) 客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
来用类图来形象地表示出它们的关系吧(对类图的了解可以参看我关于类图的blog)。
怎么咋看咋像简单工厂模式呢!没错,可以说结构型的单纯享元模式和创建型的简单工厂模式实现上非常相似,但是它的重点或者用意却和工厂模式截然不同。工厂模式的使用主要是为了使系统不依赖于实现得细节(见《深入浅出工厂模式》);而在享元模式的主要目的如前面所述:采用共享技术来避免大量拥有相同内容对象的开销。正所谓“旧瓶装新酒”阿!
再来看看复合享元模式的结构。
1) 抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。在Java中可以由抽象类、接口来担当。
2) 具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
3) 复合享元角色:它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。
4) 享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!
5) 客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
统比一下单纯享元对象和复合享元对象,里面只多出了一个复合享元角色,但是它的结构就发生了很大的变化。我们还是使用类图来表示下:
你也许又纳闷了,这个也似曾相逢!单看左半部,和简单工厂模式类似;再看右半部,怎么这么像合成模式呢(请参看关于合成模式的文章或者期待我的《深入浅出合成模式》)!合成模式用在此处就是为了将具体享元角色和复合享元角色同等对待和处理,通过将享元模式与合成模式组合在一起,可以确保复合享元中所包含的每个单纯享元都具有相同的外蕴状态,而这些单纯享元的内蕴状态往往是不同的。
四、举例
这里就以去餐馆吃饭为例详细的说明下享元模式的使用方式。去菜馆点菜吃饭的过程大家一定都是轻车熟路了,这里就不赘述。在例子中我使用了一个list来存放外蕴状态和内蕴状态的对应关系,而且提供了查询每个客人点菜情况的方法。内蕴状态在这里代表了菜肴的种类,而外蕴状态就是每盘菜肴的点菜人。
A 让我们先来看看单纯享元模式的实现吧。
先看下抽象享元角色的定义:
interface Menu
{
//规定了实现类必须实现设置内外关系的方法
public void setPersonMenu(String person , List list);
//规定了实现类必须实现查找外蕴状态对应的内蕴状态的方法
public List findPersonMenu(String person, List list);
}
这便是具体享元角色了:
class PersonMenu implements Menu
{
private String dish ;
//在构造方法中给内蕴状态附值
public PersonMenu(String dish){
this.dish = dish ;
}
public synchronized void setPersonMenu(String person , List list)
{
list.add(person);
list.add(dish);
}
public List findPersonMenu(String person, List list)
{
List dishList = new ArrayList();
Iterator it = list.iterator();
while(it.hasNext())
{
if(person.equals((String)it.next()))
dishList.add(it.next());
}
return dishList ;
}
}
享元工厂角色,这可是关键所在,大家注意看!
class FlyweightFactory
{
private Map menuList = new HashMap();
private static FlyweightFactory factory = new FlyweightFactory();
//这里还使用了单例模式,来使工厂对象只产生一个工厂实例
private FlyweightFactory(){}
public static FlyweightFactory getInstance()
{
return factory ;
}
//这就是享元模式同工厂模式的不同所在!!
public synchronized Menu factory(String dish)
{
//判断如果内蕴状态已经存在就不再重新生成,而是使用原来的,否则就重新生成
if(menuList.containsKey(dish))
{
return (Menu)menuList.get(dish);
}else{
Menu menu = new PersonMenu(dish);
menuList.put(dish,menu);
return menu;
}
}
//来验证下是不是真的少产生了对象
public int getNumber()
{
return menuList.size();
}
}
我们使用客户程序来试验下吧。
class Client
{
private static FlyweightFactory factory ;
public static void main(String[] args)
{
List list1 = new ArrayList();
factory = FlyweightFactory.getInstance();
Menu 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("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());
List list2 = list.findPersonMenu("ai921",list1);
Iterator it = list2.iterator();
while(it.hasNext())
{
System.out.println(" "+it.next());
}
}
}
这样便使用单纯享元模式实现了这些功能,但是你是不是发现一个人点了好几样菜的时候是不是使用很不方便?而这种情况正好符合复合享元模式的使用条件:复合享元中所包含的每个单纯享元都具有相同的外蕴状态,而这些单纯享元的内蕴状态往往是不同的。由于复合享元模式不能共享,所以不存在什么内外状态对应的问题。所以在复合享元类中我们不用实现抽象享元对象中的方法,因此这里采用的是透明式的合成模式。
那么下面我就使用复合享元模式在上例的基础上来实现一下。
首先要实现一个复合享元角色:
class PersonMenuMuch implements Menu
{
private Map MenuList = new HashMap();
public PersonMenuMuch(){}
//增加一个新的单纯享元对象
public void add(String key , Menu menu)
{
MenuList.put(key , menu);
}
//两个无为的方法
public synchronized void setPersonMenu(String person , List list)
{ }
public List findPersonMenu(String person, List list)
{
List nothing = null ;
return nothing ;
}
}
在工厂方法中添加一个方法,实现重载。
public Menu factory(String[] dish)
{
PersonMenuMuch menu = new PersonMenuMuch();
String key = null ;
for(int i=0 ; i<dish.length ; i++)
{
key = dish[i];
menu.add(key , this.factory(key));//调用了单纯享元角色的工厂方法
}
return menu ;
}
也许我的例子举的不太恰当,但是基本上也能看出单纯享元模式和复合享元模式在实现上的特点,如果这个目的达到了那就忘了这个糟糕的例子吧(不要让它成了你深入理解享元模式的障碍),让我们来分析下这两种模式吧。
先从复杂度上来讲,复合享元模式显而易见是比单纯享元模式复杂的。
再从享元模式的关键——共享,来分析:复合享元模式在共享上面是没有达到预期的效果,可以说是没有起到共享的目的。虽然对于它内部包含的单纯享元角色来说还是能够起到共享的作用,但是复合享元角色中一个内蕴状态和对象使用了两个Map来保存,这肯定是不会节省什么空间和对象个数的。所以我认为复合享元模式是违背享元模式初衷的。因此我们应该尽量使用单纯享元模式。
在程序中你也许注意到,我对内蕴外蕴状态对应关系的保持是采用一个list表来做的,这仅仅是个举例,你完全可以采用各种能达到目的的方式来完成。这一点也说明在享元模式中仅提供给我们怎么来吧一个对象的状态分开来达到共享,而对于关系的维护它是不关心的,也不是这个模式涉及的内容。
这样我就把享元模式使用一个例子详细的讲解了一下。如果还是不太明白的话请回味下前面的定义与结构。只有两者结合才能很好的体会到享元模式的用意。
五、使用优缺点
享元模式优点就在于它能够大幅度的降低内存中对象的数量;而为了做到这一步也带来了它的缺点:它使得系统逻辑复杂化,而且在一定程度上外蕴状态影响了系统的速度。
所以一定要切记使用享元模式的条件:
1) 系统中有大量的对象,他们使系统的效率降低。
2) 这些对象的状态可以分离出所需要的内外两部分。
外蕴状态和内蕴状态的划分以及两者关系的对应也是非常值得重视的。只有将内外划分妥当才能使内蕴状态发挥它应有的作用;如果划分失误,在最糟糕的情况下系统中的对象是一个也不会减少的!两者的对应关系的维护和查找也是要花费一定的空间(当然这个比起不使用共享对象要小得多)和时间的,可以说享元模式就是使用时间来换取空间的。在Gof的书中是使用了B树来进行对应关系查找优化。
六、总结
也许你要长叹一声:这个享元模式未必太复杂了吧!这点是不得不承认的,也许由于它的复杂,实际应用也不是很多,这是我们更加无法看清他的真面目了。不过享元模式并不是鸡肋,它的精髓——共享是对我们系统优化非常有好处的,而且这种思想已经别越来越多的应用,这应该就算是享元模式的应用了吧。如果你已经领会到了享元模式的精髓,那么也就是掌握了享元模式了!
匆匆学完了享元模式,不知道理解上有没有纰漏,希望大家能指正出来,一起共同进步!其实我一直想使用一个实际系统中或者实践中的例子来讲解享元模式,可是毕竟自己的工作经验太少了!!于是想在网上找一些灵感来,可是狂搜一阵子也没有发现什么,于是就又落俗套的使用了一个比喻的例子。如果您对此深有体会的话,还烦请不吝赐教!!
refurl:http://blog.csdn.net/ai92/article/details/224598
相关推荐
享元模式是软件设计模式中的一种结构型模式,它的主要目的是通过共享大量细粒度对象来减少内存的使用,提高系统性能。在许多场景下,尤其是处理大量相似对象时,享元模式能显著减少内存开销。这个压缩包文件...
享元模式是一种软件设计模式,它的主要目的是为了提高性能,减少对象的创建,尤其是在大量相似对象需要被创建的情况下。在给定的咖啡店案例中,享元模式的应用可以帮助优化内存使用,避免为每杯咖啡的配料表分配独立...
享元模式是一种经典的设计模式,属于结构型模式,它的核心思想是通过共享已经存在的对象来减少内存中的对象数量,从而提高系统性能。在许多场景下,特别是计算机编程中,我们可能会遇到大量的相似对象,比如在图形...
享元模式是软件设计模式中的一种结构型模式,它的主要目的是为了提高性能,尤其是在处理大量对象时。在享元模式中,通过共享技术来有效支持大量细粒度的对象,从而减少内存消耗。《设计模式之禅》这本书是设计模式...
享元模式是一种结构型设计模式,它通过共享已有对象来减少内存中对象的数量,从而达到降低内存占用的目的。在面向对象编程中,当系统需要创建大量相似对象时,享元模式能够有效地提升性能,因为大部分对象是重复的,...
享元模式是一种优化资源使用的软件设计模式,尤其适用于对象创建成本较高或系统内存有限的情况。在计算机科学中,特别是编程领域,享元模式通过共享大量相似对象来减少内存占用,提高程序性能。它通过将对象的状态...
享元模式是一种设计模式,属于构造型模式,其主要目的是减少对象的数量,通过共享大量相似对象的内部状态来节省内存。这种模式在处理大量细粒度对象时特别有用,能够有效地提高系统的性能。 享元模式的核心是...
享元模式是软件工程中一种用于优化性能的设计模式,它通过共享相似对象的实例来减少内存使用或者计算的开销。在JavaScript编程中,尤其是处理DOM操作和大量相似对象时,享元模式的使用尤为关键。 享元模式的主要...
享元模式是一种优化资源利用、减少对象创建的设计模式,它在Android开发中也有着广泛的应用。在这个"Android享元模式Demo"中,我们将探讨如何在Android环境中实现享元模式,并通过具体的实例来理解其工作原理。 享...
享元模式是一种结构型设计模式,它通过共享已有对象来减少内存中对象的数量,从而达到降低系统内存占用、提高性能的目的。在软件工程中,当系统中存在大量相似或重复的对象时,享元模式尤为适用。 享元模式的核心是...
享元模式是软件设计模式中的一种结构型模式,它的核心思想是通过共享已经存在的对象来减少内存中的对象数量,从而提高系统性能。在Java中,享元模式常用于处理大量相似对象的场景,例如在图形界面中绘制大量相似的...
在享元模式中可以共享的相同内容称为内部状态(Intrinsic State),而那些需要外部环境来设置的不能共享的内容称为外部状态(Extrinsic State),由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同...
享元模式是一种结构型设计模式,它通过共享已有对象来减少系统中对象的数量,从而达到降低内存占用的目的。在软件工程中,当一个系统需要处理大量相似对象时,享元模式能够有效地提升性能,尤其在处理轻量级对象时...
享元模式(Flyweight Pattern)是一种结构型设计模式,它能有效地减少系统中对象的数量,从而降低内存消耗,提高性能。这种模式通过共享大量相似对象的内部状态来达到这一目标,而只保留对象的外部状态在外部管理。...
享元模式是设计模式中的一种结构型模式,它主要通过共享已有对象来减少内存中对象的数量,从而提高系统性能。在C#编程中,享元模式尤其适用于那些创建大量相似对象且内存消耗较大的场景。本篇文章将深入探讨享元模式...
享元模式是一种优化资源利用的设计模式,主要用于减少创建和管理对象的数量,以达到节省内存的目的。在软件工程中,尤其是在处理大量相似对象时,享元模式能够显著提高系统的性能。享元模式通过共享那些可以共享的...
享元模式是面向对象设计中的一种结构型模式,它的主要目的是通过共享大量相似对象来减少内存的使用,提高系统的性能。在C#编程语言中,我们可以利用享元模式来优化那些具有大量实例但大部分状态可以共享的对象。在这...
java设计模式【之】享元模式【源码】【场景:多缓存可选模式】 /** * 享元模式(预加载单例) * “元” 理解为 “同一地址对象” * 运用共享技术,减少对象的创建,降低对象的数量,降低内存消耗,提高性能 * ...
享元模式 享元模式(Flyweight Pattern)是一种结构型设计模式,旨在减少大量相似对象的创建,降低系统资源的开销和内存压力。该模式通过共享已经存在的对象来大幅减少需要创建的对象数量,避免创建大量相似类带来...