垃圾回收站

June 19, 2007

重温工厂模式、装饰(Decorator)模式、组合(component)模式

[ 分类: 其他技术 ] 由 弗里曼·潘 发表于 4:06 am

工厂模式分三种,简单工厂(也叫静态工厂)模式、工厂方法(也叫动态工厂)模式、抽象工厂模式。
虽然看了许多设计模式的资料,也问了不少同行的朋友,但对工厂模式的理解感觉不够,今天又带着几个问题重温了一下:
1、为什么要使用工厂模式?
2、它能带来什么样的好处?
3、什么情况下适合作用工厂模式?
在网上搜索一下,发现有许多使用工厂模式的例子,但我觉得绝对多数都是在滥用,并没有真正理解工厂模式的设计用意,最后还是从《JAVA与模式》这本书找到比较满意的答案:
1、使用的工厂模式的原因是:
  工厂模式将产品的创建和产品的使用隔离,当产品种族比较复杂,层次比较多,工厂起到了对产品统一管理、调配的作用。
2、使用工厂模式的好处有:
a.将产品的创建和使用隔离控制
b.统一管理产品,更具灵活性
c.允许动态的增加产品种类,而不需要修改原来的代码
3、当系统需要把产品的创建和使用分开,或者需要对产品的创建过程进行统一管理、调配,工厂模式是个很好的选择,并不是一提到产品的创建就想用工厂模式,否则就是滥用。

设计模式–装饰(Decorator)模式

    这段时间,因为项目要评审,忙着准备资料,有好几天没到这里了。有一个模式我一直想好好的写,但总找不到从哪下手,可能是今天忙完了事情,突然想起一个例子可以实现,于是赶紧跑到这里写了下来,和大家分享一下。

装饰模式

意图

动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator 模式相比生成子类更为灵
活。

适用性

  在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  处理那些可以撤消的职责。
  当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

应用案例:

using System;

namespace OOD.Decorator
{
 //装饰模式实例应用
 //这里以一个宠物店为例子说明
 //作者:盛天 时间:2006-06-15

 /// 抽象产品
 public interface IPet
 {
  string Name{get;set;} //宠物的名字

  void Play(); //宠物玩耍 
 }

 //小猫
 public class Cat : IPet
 {
  #region IPet 成员

  protected string name;

  public string Name
  {
   get{return name;}
   set{name = value;}
  }

  public Cat (string strName)
  {
   Name = strName;
  }

  public void Play()
  {
   System.Console.WriteLine(”A cat is playing a ball”);
   System.Console.WriteLine(”It’s name is ” + Name);
  }

  #endregion

 }

 //小狗
 public class Dog : IPet
 {
  #region IPet 成员

  protected string name;

  public string Name
  {
   get{return name;}
   set{name = value;}
  }

  public Dog (string strName)
  {
   Name = strName;
  }

  public void Play()
  {
   System.Console.WriteLine(”A dog is playing a bone”);
   System.Console.WriteLine(”It’s name is ” + Name);
  }

  #endregion

 }

 //新开一家宠物店,专为小宠物们服务
 public interface IPetShop
 {
  IPet pet{get;set;}
  void Service();
 }

 //宠物们玩累了,这里有一个专供宠物洗澡的地方
 public class BathHouse : IPetShop
 {
  protected IPet ppet;

  public IPet pet
  {
   get{return ppet;}
   set{ppet = value;}
  }

  //好了,开始把他们放进来洗白白了
  public void Service()
  {
   System.Console.WriteLine(”Now ” + pet.Name + ” is fatigued,take a bath for it”);
  }

 }

 //宠物们玩累了,这里有一个专供宠物喂食的地方
 public class FeedHouse : IPetShop
 {
  protected IPet ppet;

  public IPet pet
  {
   get{return ppet;}
   set{ppet = value;}
  }

  //噢,小可爱们,来吃吧
  public void Service()
  {
   System.Console.WriteLine(”Now ” + pet.Name + ” is fatigued,The time is to feed.”);
  }

 }

 //调用程序
 public class Main
 {
  public Main()
  {
   //一只叫做MiMi的小猫
   Cat mimi = new Cat(”MiMi”);

   //还有一只叫做BenBen的小狗
   Dog benben = new Dog(”BenBen”);

   //它们在玩耍
   mimi.Play();
   benben.Play();

   //一小时后
   System.Console.WriteLine(”One hour later”);

   //这时mimi饿了,准备去吃东西,去哪吃呢?那还用问,当然是新开的宠物店, 听说那里有许多好吃的东西
   IPetShop petshop;
   petshop = new FeedHouse();
   petshop.pet = mimi;  //MiMi进来食堂(Feedhouse)啦
   petshop.Service();  //食堂为MiMi准备了好吃的东西

   //另外一边,BenBen玩耍是满头大汗,这么热的天,得去洗个澡。
   petshop = new BathHouse();
   petshop.pet = benben; //BenBen来到了澡堂(BathHouse)
   petshop.Service();  //澡堂的服务员给BenBen洗了个澡,现在舒服极了。

   petshop = null;
   mimi = null;
   benben = null;

  }
 }

 }

  结语:装饰模式最大的特点是动态的改变对象的功能,如上例中,创建完一只宠物猫后,我们可以根据实际情况的不同,让它选择拥有洗澡或是吃东西的功能,装饰模式在实际应用中是非常实用的。

设计模式–组合(component)模式

   将对象以树形结构组织起来,以达成“部分-整体” 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。

意图
    将对象组合成树形结构以表示“部分-整体”的层次结构。Composite 使得用户对单个对象和组合对象的使用具有一致性。

适用性
    你想表示对象的部分-整体层次结构。
    你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

案例应用

    下面是一个WEB的树型导航菜单的实例

using System;

namespace OOD.Tree
{
 public interface INode
 {
  string Name
  {
   get;
   set;
  }
  string Link
  {
   get;
   set;
  }
  void Add(INode n);
  void Remove(INode n);
  void Display();
 }

 public class Leaf : INode
 {
  protected string name;
  protected string link;

  public Leaf(string n,string l)
  {
   this.Name = n;
   this.Link = l;
  }
  #region INode 成员

  public string Name
  {
   get
   {
    return name;
   }
   set
   {
    name = value;
   }
  }

  public string Link
  {
   get
   {
    return link;
   }
   set
   {
    link = value;
   }
  }

  public void Add(INode n)
  {
   Console.WriteLine(”Leaf Con’t Add node”);
  }

  public void Remove(INode n)
  {
   Console.WriteLine(”Leaf Con’t Remove node”);
  }

  public void Display()
  {

   //这里使用ASP.NET的Response对象输出结果
   System.Web.HttpContext.Current.Response.Write(”<li><a href=” + this.Link + “>” + this.Name + “</a></li>\n”);
  }

  #endregion
 }

 public class Folder : INode
 {
  protected string name;
  protected string link;

  protected System.Collections.ArrayList NodeList = new System.Collections.ArrayList();

  public Folder(string n)
  {
   this.Name = n;
   this.Link = “”;
  }

  #region INode 成员

  public string Name
  {
   get
   {
    return name;
   }
   set
   {
    name = value;
   }
  }

  public string Link
  {
   get
   {
    return link;
   }
   set
   {
    link = value;
   }
  }

  public void Add(INode n)
  {
   NodeList.Add(n);
  }

  public void Remove(INode n)
  {
   NodeList.Remove(n);
  }

  public void Display()
  {
   ////这里使用ASP.NET的Response对象输出结果

   System.Web.HttpContext.Current.Response.Write(”<li>” + name + “</li>\n”);

   System.Web.HttpContext.Current.Response.Write(”<ul>\n”);

   foreach(INode node in NodeList)
   {
    node.Display();
   }

   System.Web.HttpContext.Current.Response.Write(”</ul>\n”);
  }

  #endregion
 }

}

客户端调用代码:

   Response.Clear();

   OOD.Tree.Folder root = new OOD.Tree.Folder(”管理中心”);

   OOD.Tree.Folder member = new OOD.Tree.Folder(”会员管理”);
   member.Add(new OOD.Tree.Leaf(”修改个人资料”,”01.htm”));
   member.Add(new OOD.Tree.Leaf(”收短信息”,”02.htm”));
   root.Add(member);

   OOD.Tree.Folder product = new OOD.Tree.Folder(”产品管理”);
   product.Add(new OOD.Tree.Leaf(”产品分类”,”11.htm”));
   product.Add(new OOD.Tree.Leaf(”产品列表”,”12.htm”));
   product.Add(new OOD.Tree.Leaf(”订单查询”,”13.htm”));
   root.Add(product);

   root.Add(new OOD.Tree.Leaf(”退出系统”,”00.htm”));

   root.Display();

输出结果:

10c17a2ebf4

如果结合javascript就可以做出一个漂亮的树型导航菜单,有兴趣的朋友可以自己动手试试。

设计模式–抽象工厂模式

   我们都知道,软件设计一般遵守“开闭原则”,意思是说我们设计出来的程序模块,在功能扩展性方面是开放的,可以随时进行扩展;而在程序模块的修改性上是关闭的,不允许修改原来的程序模块。经验告诉我们,修改原来的一些程序模块,往往会扯及其他模块,这是非常危险的,在一个复杂的系统中,你很可能不知道有多少个模块和将要修改的模块是关系的。但有些时候,我们不得不修改原来的程序设计,在这里我们提供了一个解决问题的办法,就是今天要说的抽象工厂设计模式

适用性
  一个系统要独立于它的产品的创建、组合和表示时。
  一个系统要由多个产品系列中的一个来配置时。
  当你要强调一系列相关的产品对象的设计以便进行联合使用时。
  当你提供一个产品类库,而只想显示它们的接口而不是实现时。

案例应用
  做电子商务网站经验遇到这样的问题,刚开始的时候只考虑了有两种会员:普通会员和VIP会员,随着网站的发展,需要添加更多种类型的会员。为了不修改原有的代码,我们使用抽象工厂设计模式,代码如下:
//             AbstractFactory.cs               //
//     我们先定义两种会员:普通会员和VIP会员      //
//////////////////////////////////////////////////
using System;
namespace OOD.AbstractFactory
{

 /// <summary>
 /// 抽象会员接口
 /// </summary>
 public interface IMember
 {
  int UserID
  {
   get;
   set;
  }

  string UserName
  {
   get;
   set;
  }

  string UserPWD
  {
   get;
   set;
  }

  string UserRight
  {
   get;
   set;
  }

  bool Login();

 }

 /// <summary>
 /// 抽象工厂接口
 /// </summary>
 public interface IMemberFactory
 {
  IMember CreateMember(string username,string userpwd);
 }

 /// <summary>
 /// 具体会员:普通会员
 /// </summary>
 public class NormalMember : IMember
 {
  private int userid;
  private string username;
  private string userpwd;
  private string userright;

  #region IMember 成员

  public int UserID
  {
   get
   {
    // TODO:  添加 NormalMember.UserID getter 实现
    return userid;
   }
   set
   {
    // TODO:  添加 NormalMember.UserID setter 实现
    userid = value;
   }
  }

  public string UserName
  {
   get
   {
    return username;
   }
   set
   {
    username = value;
   }
  }

  public string UserPWD
  {
   get
   {
    return userpwd;
   }
   set
   {
    userpwd = value;
   }
  }

  public string UserRight
  {
   get
   {
    return userright;
   }
   set
   {
    userright = value;
   }
  }

  public NormalMember()
  {
   ;
  }

  public bool Login()
  {
   //根据数据库检查UserName和UserPWD,如果身份正确,则保存UserID和UserRight
   UserID = 01;
   UserRight = “拥有普通会员权限”;
   return true;

   //如果不正确返回false;
   //return false;
  }

  #endregion

 }

 /// <summary>
 /// 具体会员:VIP会员
 /// </summary>
 public class VIPMember : IMember
 {
  private int userid;
  private string username;
  private string userpwd;
  private string userright;

  #region IMember 成员

  public int UserID
  {
   get
   {
    // TODO:  添加 NormalMember.UserID getter 实现
    return userid;
   }
   set
   {
    // TODO:  添加 NormalMember.UserID setter 实现
    userid = value;
   }
  }

  public string UserName
  {
   get
   {
    return username;
   }
   set
   {
    username = value;
   }
  }

  public string UserPWD
  {
   get
   {
    return userpwd;
   }
   set
   {
    userpwd = value;
   }
  }

  public string UserRight
  {
   get
   {
    return userright;
   }
   set
   {
    userright = value;
   }
  }

  public VIPMember()
  {
   ;
  }

  public bool Login()
  {
   //根据数据库检查UserName和UserPWD,如果身份正确,则保存UserID和UserRight
   UserID = 02;
   UserRight = “拥有VIP会员权限”;
   return true;

   //如果不正确返回false;
   //return false;
  }

  #endregion

 }

 /// <summary>
 /// 具体工厂:普通会员生产工厂
 /// </summary>
 public class NormalMemberFactory : IMemberFactory
 {
  #region IMemberFactory 成员

  public IMember CreateMember(string username,string userpwd)
  {
   NormalMember objMember = new NormalMember();
   objMember.UserName = username;
   objMember.UserPWD = userpwd;
   objMember.Login();

   return objMember;
  }

  #endregion

 }

 /// <summary>
 /// 具体工厂:VIP会员生产工厂
 /// </summary>
 public class VIPMemberFactory : IMemberFactory
 {
  #region IMemberFactory 成员

  public IMember CreateMember(string username,string userpwd)
  {
   VIPMember objMember = new VIPMember();
   objMember.UserName = username;
   objMember.UserPWD = userpwd;
   objMember.Login();

   return objMember;
  }

  #endregion

 }

}

客户端调用代码:
   Response.Clear();
   IMember objMember = new NormalMemberFactory().CreateMember(”",”");
   Response.Write(”<P>已经创建了一个普通会员,权限为:”  + objMember.UserRight);
   IMember objMember = new VIPMemberFactory().CreateMember(”",”");
   Response.Write(”<P>已经创建了一个VIP会员,权限为:”  + objMember.UserRight);

执行结果如下:
已经创建了一个普通会员,权限为:拥有普通会员权限
已经创建了一个VIP会员,权限为:拥有VIP会员权限 

    这时我们如果需要添加一种会员:“外星人会员”,要求不能修改原代码,于是我们另外创建了一个文件:newMember.cs
using System;
namespace OOD.AbstractFactory
{

 /// <summary>
 /// 增加一具体会员:外星人会员
 /// </summary>
 public class ExtraMember : IMember
 {
  private int userid;
  private string username;
  private string userpwd;
  private string userright;

  #region IMember 成员

  public int UserID
  {
   get
   {
    // TODO:  添加 NormalMember.UserID getter 实现
    return userid;
   }
   set
   {
    // TODO:  添加 NormalMember.UserID setter 实现
    userid = value;
   }
  }

  public string UserName
  {
   get
   {
    return username;
   }
   set
   {
    username = value;
   }
  }

  public string UserPWD
  {
   get
   {
    return userpwd;
   }
   set
   {
    userpwd = value;
   }
  }

  public string UserRight
  {
   get
   {
    return userright;
   }
   set
   {
    userright = value;
   }
  }

  public ExtraMember()
  {
   ;
  }

  public bool Login()
  {
   //根据数据库检查UserName和UserPWD,如果身份正确,则保存UserID和UserRight
   UserID = 03;
   UserRight = “在地球观光的权限”;
   return true;

   //如果不正确返回false;
   //return false;
  }

  #endregion

 }

 /// <summary>
 /// 具体工厂:外星人会员生产工厂
 /// </summary>
 public class ExtraMemberFactory : IMemberFactory
 {
  #region IMemberFactory 成员

  public IMember CreateMember(string username,string userpwd)
  {
   ExtraMember objMember = new ExtraMember();
   objMember.UserName = username;
   objMember.UserPWD = userpwd;
   objMember.Login();

   return objMember;
  }

  #endregion

 }

}

调用新的会员方法是这样:
   IMember objMember = new ExtraMemberFactory().CreateMember(”",”");
   Response.Clear();
   Response.Write(”<P>已经创建了一个外星人会员,权限为:”  + objMember.UserRight);

运行结果:
已经创建了一个外星人会员,权限为:在地球观光的权限

设计模式–单件模式

作用
  保证一个类只有一个实例,并提供一个访问它的全局访问点。

适合于
当一个类只能有一个实例,且客户可以从一个众所周知的访问点进行访问;
  当这个唯一的实例应该是可以通过子类化可以扩展的,并且客户无需要修改代码就能使用这个已扩展的实例时。

案例应用

   很多情况下,我们需要一个Singleton窗体,比如,任务管理器就可以是windows应用系统中的一个Singleton,于是我们这样做:在窗体类的实现中添加一个静态指向单件的成员,提供一个静态的CreateInstance方法,当第一次调用此方法时创建单件。另外还要处理Closing事件,以使点击窗体右上角的“×”时,隐藏窗体。每次需要实现一个单件窗体时都要这么做,于是我决定写一个基类ISingletonDisplayer,一个窗体不用作任何改变,只需要将原来的基类Form改为ISingletonDisplayer就变成一个Singleton窗体,这就很方便了。

public class ISingletonDisplayer : Form  //从Form继承
{
 private static ISingletonDisplayer singleton = null ;
 private static bool toClean = false ;

 protected ISingletonDisplayer()
 {
  this.Closing += new System.ComponentModel.CancelEventHandler(ISingletonDisplayer_Closing);
 }

 #region static for singleton

 #region GetSingleton
 public static ISingletonDisplayer GetSingleton()
 {
  if(ISingletonDisplayer.singleton != null)
  {
   ISingletonDisplayer.singleton.Visible = true ;
  }
  return ISingletonDisplayer.singleton ;
 }

 #endregion

 #region ReCreateSingleton

 //formOnUI存在是因为窗体只能在主线程(UI线程)中创建。
 public static void ReCreateSingleton(Type targetType ,object[] args , Form formOnUI)
 {
  Type supType = typeof(ISingletonDisplayer) ;
  if(! supType.IsAssignableFrom(targetType))
  {
   throw new Exception(”Target type is not derived from ISingletonDisplayer !”) ;
  }

  if(formOnUI.InvokeRequired)
  {
   object[] paras = {targetType ,args ,formOnUI} ;
   formOnUI.Invoke(new CBackCreateForm(ISingletonDisplayer.ReCreateSingleton) ,paras) ;
  }
  else
  {
   try
   {
    ISingletonDisplayer.toClean = false ;
    ISingletonDisplayer.singleton  = (ISingletonDisplayer)Activator.CreateInstance(targetType ,args) ;
    ISingletonDisplayer.singleton.Visible = false ;
   }
   catch(Exception ee)
   {
    throw ee ;
   }
  }
 }
 #endregion

 #region DestroySingleton
 public static void DestroySingleton()
 {
  ISingletonDisplayer.toClean = true ;
  if(ISingletonDisplayer.singleton != null)
  {
   ISingletonDisplayer.singleton.Close() ;
   ISingletonDisplayer.singleton = null ;
  }
 }
 #endregion

 #endregion

 #region ISingletonDisplayer_Closing
 private void ISingletonDisplayer_Closing(object sender, System.ComponentModel.CancelEventArgs e)
 {
  if(! ISingletonDisplayer.toClean)
  {
   this.Visible = false ;
   e.Cancel = true ;
   return ;
  }
 }
 #endregion
}

internal delegate void CBackCreateForm(Type targetType ,object[] args  , Form formOnUI) ;

* 使用方法:
* 如果一个Displayer需要以Singleton模式呈现,那么可以先以常规的方式设计Displayer,设计过程中不需要涉及任何与单件
* 相关的东西。设计完后,只要将Displayer的基类由Form改为ISingletonDisplayer即可。接下来即可以单件的模式使用Displayer了。
*
* 如:
* public class AppServersInfoForm2 : ISingletonDisplayer{}
* object[] args = {obj} ;
* ISingletonDisplayer.ReCreateSingleton(typeof(AppServersInfoForm2) ,args ,formOnUI) ;

这样每个单件都有自己的静态实例,就不会相互干扰了。


0 条评论 »

还没有人对这篇文章发表评论,赶紧留一个吧。

RSS feed for comments on this post. TrackBack URI

相关文章:
  • 暂时没有相关的文章
  • 发表评论