现在的位置: 首页 > 综合 > 正文

泡妞之享元模式

2013年09月15日 ⁄ 综合 ⁄ 共 3775字 ⁄ 字号 评论关闭

类关系图

类角色说明
抽象享元(Flyweight)角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过调用商业方法以参数形式传入。

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

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

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

意图
Flyweight 在拳击比赛中指最轻量级,即"蝇量级",有些作者翻译为"羽量级"。这里使用"享元模式"更能反映模式的用意。
享元模式以共享的方式高效地支持大量的细粒度对象。享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)。内蕴状态是存储在享元对象内部并且不会随环境改变而改变。因此内蕴状态并可以共享。 外蕴状态是随环境改变而改变的、不可以共享的状态。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态与内蕴状态是相互独立的。

适用性
当以下所有的条件都满足时,可以考虑使用享元模式:
· 一个系统有大量的对象。
· 这些对象耗费大量的内存。
· 这些对象的状态中的大部分都可以外部化。
· 这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
· 软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
满足以上的这些条件的系统可以使用享元对象。
最后,使用享元模式需要维护一个记录了系统已有的所有享元的表,而这需要耗费资源。因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。

泡妞的例子
FLYWEIGHT —每天跟MM发短信,手指都累死了,最近买了个新手机,可以把一些常用的句子存在手机里,要用的时候,直接拿出来,在前面加上MM的名字就可以发送了,再不用一个字一个字敲了。共享的句子就是Flyweight,MM的名字就是提取出来的外部特征,根据上下文情况使用。

泡妞的代码

using System;
using System.Collections;
//抽象享元(Flyweight)角色
public abstract class 抽象短信
{
    
public abstract void 发送();
}

//实际享元角色 如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。
public class 短信:抽象短信
{
    
string msg;
    
public 短信(string strmsg)
    
{
        msg
=strmsg;
    }

    
public override void 发送()
    
{
        Console.WriteLine(msg);
    }

}

//享元工厂类当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个复合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
public class 手机
{
    
private Hashtable 短信模版 = new Hashtable();
    
//以内蕴状态为参数检查工厂列表中是否含有具有相同内蕴状态的对象,有则返回该对象,否则创建新对象
    public 抽象短信 GetMSG(string msg)
    
{
     
if(! 短信模版.ContainsKey(msg))
       短信模版.Add(msg, 
new 短信(msg));
 
         
return ((抽象短信)短信模版[msg]);
   }

    
public int 模版容量()
    
{
     
return 短信模版.Count;
   }

}

//本角色需要维护一个对所有享元对象的引用。本角色需要自行存储所有享元对象的外蕴状态
public class Client
{
    
private static 手机 新手机;
    
private static int msgcount = 0;    //自行存储的外蕴状态
    public static void Main()
    
{
        新手机
=new 手机();
        发短信(
"Lily","我爱你啊!");
        发短信(
"Mary","我们做好朋友吧!");
        发短信(
"Lisa","今晚有空么?");
        发短信(
"Mary","我爱你啊!");
        
        Console.WriteLine(
"已发送"+msgcount.ToString()+"条短信息!");
        Console.WriteLine(
"手机中共有"+Convert.ToString(新手机.模版容量())+"条模版信息!");
    }

    
    
private static void 发短信(string mm,string msg)
    
{
     抽象短信 短信息 
= 新手机.GetMSG(msg);
     Console.Write(mm);
     短信息.发送();
 
     msgcount
++;
   }

}

享元模式
享元模式:FLYWEIGHT在拳击比赛中指最轻量级。享元模式以共享的方式高效的支持大量的细粒度对象。享元模式能做到共享的关键是区分内蕴状态和外蕴状态。内蕴状态存储在享元内部,不会随环境的改变而有所不同。外蕴状态是随环境的改变而改变的。外蕴状态不能影响内蕴状态,它们是相互独立的。将可以共享的状态和不可以共享的状态从常规类中区分开来,将不可以共享的状态从类里剔除出去。客户端不可以直接创建被共享的对象,而应当使用一个工厂对象负责创建被共享的对象。享元模式大幅度的降低内存中对象的数量。

优缺点
享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:
· 享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
· 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

说明
事物的可以被分为内部状态和外部状态。内部状态标识对象的内部特性,保持相对稳定;而外部状态实现对象的外部功能,随着外部环境的变化而改变。对象的内部状态和外部状态相对独立,不会互相影响。
这里再说几个享元模式应用的例子,都是网上很流行的例子
咖啡摊的例子
咖啡摊(Coffee Stall)所使用的系统里,有一系列的咖啡"风味(Flavor)"。客人到摊位上购买咖啡,所有的咖啡均放在台子上,客人自己拿到咖啡后就离开摊位。咖啡有内蕴状态,也就是咖啡的风味;咖啡没有环境因素,也就是说没有外蕴状态。如果系统为每一杯咖啡都创建一个独立的对象的话,那么就需要创建出很多的细小对象来。这样就不如把咖啡按照种类(即"风味")划分,每一种风味的咖啡只创建一个对象,并实行共享。
使用咖啡摊主的语言来讲,所有的咖啡都可按"风味"划分成如Capucino、Espresso等,每一种风味的咖啡不论卖出多少杯,都是全同、不可分辨的。所谓共享,就是咖啡风味的共享,制造方法的共享等。因此,享元模式对咖啡摊来说,就意味着不需要为每一份单独调制。摊主可以在
需要时,一次性地调制出足够一天出售的某一种风味的咖啡。

编辑器的例子
在编辑器系统中大量使用。一个文本编辑器往往会提供很多种字体,而通常的做法就是将每一个字母做成一个享元对象。享元对象的内蕴状态就是这个字母,而字母在文本中的位置和字模风格等其他信息则是外蕴状态。比如,字母 a可能出现在文本的很多地方,虽然这些字母 a
的位置和字模风格不同,但是所有这些地方使用的都是同一个字母对象。这样一来,字母对象就可以在整个系统中共享。

员工对象的例子
一个员工对象,其名字、性别等个人情况相对稳定,属于员工对象的内部状态,其工作角色,职务,薪酬等随环境变化,属于外部状态。
通俗的解释:
抽象享元(Flyweight)角色:定义员工对象基本的行为
实际享元角色:有内部状态属性,在初始化的时候对其内部属性进行赋值
享元工厂角色:管理实际享元角色,如果客户端需要新建一个实际享元角色,那么先检查是否已经有具有相同内部属性的实际享元角色存在,如果存在则返回已存在的对象,否则则新建。
 

【上篇】
【下篇】

抱歉!评论已关闭.