举例
这里就以去餐馆吃饭为例详细的说明下享元模式的使用方式。去菜馆点菜吃饭的过程大家一定都是轻车熟路了,这里就不赘述。在例子中我使用了一个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;
menu.add(key , this.factory(key));//调用了单纯享元角色的工厂方法
}
return menu ;
}
也许我的例子举的不太恰当,但是基本上也能看出单纯享元模式和复合享元模式在实现上的特点,如果这个目的达到了那就忘了这个糟糕的例子吧(不要让它成了你深入理解享元模式的障碍),让我们来分析下这两种模式吧。
先从复杂度上来讲,复合享元模式显而易见是比单纯享元模式复杂的。
再从享元模式的关键——共享,来分析:复合享元模式在共享上面是没有达到预期的效果,可以说是没有起到共享的目的。虽然对于它内部包含的单纯享元角色来说还是能够起到共享的作用,但是复合享元角色中一个内蕴状态和对象使用了两个Map来保存,这肯定是不会节省什么空间和对象个数的。所以我认为复合享元模式是违背享元模式初衷的。因此我们应该尽量使用单纯享元模式。
在程序中你也许注意到,我对内蕴外蕴状态对应关系的保持是采用一个list表来做的,这仅仅是个举例,你完全可以采用各种能达到目的的方式来完成。这一点也说明在享元模式中仅提供给我们怎么来吧一个对象的状态分开来达到共享,而对于关系的维护它是不关心的,也不是这个模式涉及的内容。
这样我就把享元模式使用一个例子详细的讲解了一下。如果还是不太明白的话请回味下前面的定义与结构。只有两者结合才能很好的体会到享元模式的用意。
享元模式: 以共享的方式高效地支持大量的细粒度对象。
享元对象的状态:
1:内蕴状态(Internal State)内蕴状态存储在享元对象内部且不会随环境改变而改变。因此内蕴状态并可以共享。
2:外蕴状态(External State)。外蕴状态是随环境改变而改变的、不可以共享的状态。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态与内蕴状态是相互独立的。
享元模式的应用条件:
1: 一个系统有大量的对象。
2:这些对象耗费大量的内存。
3:这些对象的状态中的大部分都可以外部化。
4:这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
5:软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
享元模式的优点:
大幅度地降低内存中对象的数量。
享元模式的缺点:
1:享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
2:享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。
总结: 享元模式一般是解决系统性能问题的,所以经常用于底层开发,在项目开发中并不常用。
[ 本帖最后由 鲁蒙 于 2011-10-01 14:03 编辑 ] |