.Net中的设计模式——Factory Method模式

一、模式概述

也许Factory Method模式是设计模式中应用最广泛的模式。在面向对象的设计中,关于对象的管理是其核心所在,而其中对象的创建则是对象管理的第一步。对象的创建非常简单,在C#中,只需要应用new操作符调用对象的构造函数即可,然而创建对象的时机却非常重要。 

首先我们从对象的特征来看,代表抽象关系的类型,如接口和抽象类,是不能创建的,换句话说,我们要创建的对象都是与具体的对象类型有关。因此,对象的创建工作必然涉及到设计中的实现细节,从而导致创建者与具体的被创建者之间耦合度增强。举例来说,如果在一个项目中我们需要创建一些图形对象,例如Circle、Square。这些对象的结构如下:

factory1.GIF

这个结构是非常符合OO思想的,它通过IShape接口将Square和Circle对象抽象出来,根据多态的原理,我们完全可以在程序中用IShape来代替具体的Square和Circle类,从而将具体的对象类型绑定留到运行时。然而,上文说到,接口对象是不能创建的,因此,项目一旦要创建IShape类型的对象,必然要针对具体的对象Square或Circle进行创建操作。例如:
    IShape shape = new Square(); 

如果是开发一个图形工具,诸如Square和Circle之类的对象,其创建工作必然非常频繁。可以设想,在这个项目的各个模块中,将会大量充斥着如上的代码行,导致的结果是各个模块无法与Square对象结耦,这意味着,如果我们改变创建的对象为Circle,就需要修改所有调用new Square()操作的模块。这既加大了工作量,同时也导致了项目的不可扩展性,以及模块的不可重用性。而对于图形对象的抽象IShape来说,也是不必要而失败的。

在面向对象的设计中,我们常常将可能变化的操作进行封装,封装的内容可能仅是某种行为,也可能是一种状态,或者是某些职责。而在当前的案例中,我们需要将对象的创建行为进行封装,这就引入了Factory Method模式。此时的对象就是Factory要生产的产品。既然产品有两种,相对应的工厂也应该是两个,即SquareFactory和CircleFactory。在Factory Method模式中,工厂对象的结构应与产品的结构平行,并与之一一对应,所以,对这两个工厂而言,还需要为其抽象出一个共同的工厂接口IShapeFactory:

factory2.GIF

代码如下:
public interface IShapeFactory
{

    IShape CreateShape();

}
public class SquareFactory:IShapeFactory
{
    public IShape CreateShape()
    {
         return new Square();
    }
}
public class CircleFactory:IShapeFactory
{
    public IShape CreateShape()
    {
      return new Circle();
    }
}

通过Factory Method模式,我们完成了对象创建的封装,将前面诸如IShape shape = new Square()的代码全部移到了各自的工厂对象中,并放到CreateShape()方法中实现。整个结构如下图所示:

factory3.GIF

请注意CreateShape()方法的返回类型是IShape类型,这就有效地避免了工厂对象与具体产品对象的依赖。

也许会有人认为,虽然通过工厂方法,将创建IShape对象的职责转交给工厂对象,然而在工厂类的结构中,仍然存在具体的工厂类对象。如此以来,虽然我们解除了模块与具体Shape对象的依赖,却增加了对具体工厂对象的依赖,这会带来何种益处?

让我们从对象创建的频率来分析。对于一个图形工具而言,IShape对象的创建无疑是频繁的,最大的可能性是在这个项目的各个模块中都可能存在创建IShape对象的需要。而工厂对象则不尽然,我们完全可以集中在一个模块中,初始化这个工厂对象,而在需要IShape对象的时候,直接调用工厂实例的CreateShape()就可以达到目的。

举例来说,假设在图形工具中,有三个模块:ModuleA,ModuleB,ModuleC;这三个模块中都需要创建Square对象,则按照原来的设计方案,这三个模块都包含这样一行代码:
IShape shape = new Square();

此时,与Square对象有依赖关系的就包括了ModuleA,ModuleB,ModuleC三个模块。如果我们需要修改shape对象为Circle类型,则这个变动无疑会影响到上述的三个模块。现在,我们引入Factory Method模式,并增加一个模块名为ModuleFactory,在这个模块中,我们创建一个工厂对象:
IShapeFactory shapeFactory = new SquareFactory();

如此以来,原来的三个模块有关Square对象的创建,就相应地修改为:
IShape shape = shapeFactory.CreateShape();

此时,即使需求发生改变,需要对shape对象进行修改,那么我们只需要修改ModuleFactory模块中的代码:
IShapeFactory shapeFactory = new CircleFactory();

而ModuleA,ModuleB,ModuleC三个模块则根本不需要作任何改变。如此的设计改进,虽然在项目中增加了三个工厂对象,并引入了ModuleFactory,但它却完成了ModuleA,ModuleB,ModuleC与具体的Square对象的解耦,从而将这三个模块与产品对象的依赖性转嫁到ModuleFactory上。如此以来,牵一发而不动其全身,极大地提高了模块的重用性。

从上述的分析可知,引入工厂对象并不是简单地为产品建立相应的工厂,而是要注意划分各个模块的职责,将工厂对象的创建放到合适的地方。最佳方案莫过于将创建工厂对象的职责集中起来,放到一个模块中;而不是在需要创建产品时,才创建工厂对象。错误的例子是在创建产品时将工厂对象的创建于产品对象的创建放在一起,并分布在各个模块中:
IShapeFactory shapeFactory = new SquareFactory();
IShape shape = shapeFactory.CreateShape();

这样的做法,则引入Factory Method模式,无异于画蛇添足了。

二、.Net Framework中的Factory Method模式

Factory Method模式在项目设计中应用非常广泛,在.Net Framework中自然也不例外。例如,在.Net中为处理Web请求,在框架类库中提供了基类WebRequest。它能够通过传入的Uri对象,创建基于不同协议的Web请求。例如,当Uri的前缀为“https://”或“http://”时,则返回HttpWebRequest对象,如果是“file://”,则返回FileWebRequest对象。HttpWebRequest和FileWebRequest对象是WebRequest的派生类。WebRequest的类结构如图:

factory4.GIF

如何创建一个WebRequest的实例呢?我们可以直接调用该类的静态方法Create():
WebRequest myRequest = WebRequest.Create(“http://www.cnblogs.com”); 

然后我们可以根据该WebRequest对象获得WebResponse:
WebResponse myResponse = myRequest.GetResponse();
……
myResponse.Close();

从上面一段代码来看,Create()静态方法似乎是简单工厂模式的一种实现,它可以根据方法传递进来的参数判断WebRequest的类型,创建具体的WebRequest对象,并返回该对象。那么最简单的实现方法,就是通过if/else条件判断参数的类型,以决定创建的对象类型。显然,这并非一个好的方法,它直接导致的就是WebRequest的具体子类与其静态方法Create()直接依赖,一旦增加新的WebRequest子类,必然要修改Create()方法。

既然涉及到对象的创建,最好的方式就是使用Factory Method模式。在.Net中,为创建WebRequest对象提供了一个专门的工厂接口IWebRequestCreate,该接口仅有一个方法,即Create()方法:
public interface IWebRequestCreate
{      
        WebRequest Create(Uri uri);
}

对应不同的Web请求,工厂模式为其提供了不同的具体工厂类,这些类均实现了IWebRequestCreate接口。例如HttpRequestCreator:
internal class HttpRequestCreator : IWebRequestCreate
{
        internal HttpRequestCreator() {} 

        public WebRequest Create( Uri Uri )
        {
            return new HttpWebRequest(Uri);
        }
}

还有类FileRequestCreator:
internal class FileWebRequestCreator : IWebRequestCreate
{
        internal FileWebRequestCreator() {}

        public WebRequest Create(Uri uri)
        {
            return new FileWebRequest(uri);
        }
}

这些类都实现了接口IWebRequestCreate的Create()方法,返回各自对应的WebRequest对象,即工厂模式所要生产的产品。

这样,我们就为产品类建立了一个和其完全一一对应的工厂类结构:

factory5.GIF

请注意工厂类和产品类之间,只存在接口IWebRequestCreate和抽象类WebRequest之间的依赖关系,这正是OOP中面向抽象编程的实质。

三、进一步探索

根据前面的描述,当我们在创建WebRequest对象时,需要在系统中预先创建其对应的工厂类对象。如下所示:
IWebRequestCreate webRequestCreate = new HttpRequestCreator();

然而与一般对象不同的是,WebRequest的类型是随时可能变化的,这就导致其对应的工厂类型也会经常改变。如果将上面的代码写到一个专门的模块中,并供客户端修改,会缺乏一定的灵活性。并且对于客户而言,WebRequest对象的创建实在太麻烦了。因为.Net Framework是一个类库,而类库的设计理念,就是要尽可能让用户更容易更方便地使用类库,至于内部的实现细节,用户是不用理会的。因此,.Net对Factory Method模式进行了一些加工。下面,我将对WebReuest的创建过程基于.Net的实现进行深入分析。

前面提到,WebRequest是一个抽象类,但它提供了一个静态方法Create(),能够根据方法中传入的Uri地址,创建相对应的WebRequest对象。它的实现代码如下:
public static WebRequest Create(Uri requestUri)
{
 if (requestUri == null) {
      throw new ArgumentNullException("requestUri");
 }
 return Create(requestUri, false);
}

该方法实质是调用了WebRequest中的私有静态方法Create():
private static WebRequest Create(Uri requestUri, bool useUriBase)
{
 string LookupUri;
 WebRequestPrefixElement Current = null;//①
 bool Found = false;

 if (!useUriBase) {
      LookupUri = requestUri.AbsoluteUri;
 }
 else {
     LookupUri = requestUri.Scheme + ':';
 }

 int LookupLength = LookupUri.Length;
 ArrayList prefixList = PrefixList; //②           

 for (int i = 0; i < prefixList.Count; i++)
 {
       Current = (WebRequestPrefixElement)prefixList[i]; //④    

       // See if this prefix is short enough.
       if (LookupLength >= Current.Prefix.Length)
  {
           // It is. See if these match.
           if (String.Compare(Current.Prefix,0,LookupUri,0,Current.Prefix.Length,true,CultureInfo.InvariantCulture) == 0)
   {
                  Found = true;
                  break;
           }
        }
 }
    if (Found)
 {
        return Current.Creator.Create(requestUri); //③
   }

   throw new NotSupportedException(SR.GetString(SR.net_unknown_prefix));
}

注意该方法中被我标注的几行代码。第①行代码定义了一个WebRequestPrefixElement对象Current,第②行代码则定义了一个ArrayList对象,并将一个已经存在的ArrayList对象PrefixList赋给它。第③行代码则通过Current对象的Creator字段来完成创建工作。整体来讲,该方法就是在一个ArrayList中,根据参数Uri的值进行查找,如果找到,则创建相关的对象并返回,否则抛出异常。

现在我们需要明白两个问题:
1、WebRequestPrefixElement类的定义什么?而该类型的Creator字段又属于什么类型?
2、PrefixList对象存储的内容是什么?

我们首先看看WebRequestPrefixElement类的定义:
internal class WebRequestPrefixElement 
{
        public string Prefix;
        public IWebRequestCreate Creator;
        public WebRequestPrefixElement(string P, IWebRequestCreate C)
 {
            Prefix = P;
            Creator = C;
        }
}

很显然,该类仅仅是提供一个将Uri前缀与IWebRequestCreate类型关联的一个简单对象而已。而Creator字段的类型正是IWebRequestCreate类型。Uri和IWebRequestCreate的关联对象,可通过WebRequestPrefixElement的构造函数来传入。

那么,Current.Creator.Create(requestUri)创建对象的实质,就是通过调用IWebRequestCreate类型对象的工厂方法,来完成对WebRequest对象的创建。然而,究竟调用的是哪一个具体工厂类呢?也就是Current字段,其代表的IWebRequestCreate对象究竟是什么?

根据第④行代码,Current的值是从prefixList列表中获得的IWebRequestCreate对象。而prefixList值在该方法中就是PrefixList对象。而PrefixList其实是WebRequest类的一个私有属性:
private static ArrayList PrefixList
{
 get
 {
      if (s_PrefixList == null)
  {
           lock (typeof(WebRequest))
   {
                 if (s_PrefixList == null)
    {
                     GlobalLog.Print("WebRequest::Initialize(): calling ConfigurationSettings.GetConfig()");
                     ArrayList prefixList = (ArrayList)ConfigurationSettings.GetConfig("system.net/webRequestModules");

                     if (prefixList == null)
     {
                           GlobalLog.Print("WebRequest::Initialize(): creating default settings");
                           HttpRequestCreator Creator = new HttpRequestCreator();

                           // longest prefixes must be the first
                           prefixList = new ArrayList();                             
                           prefixList.Add(new WebRequestPrefixElement("https", Creator)); // [0]
                           prefixList.Add(new WebRequestPrefixElement("http", Creator));  // [1]
                           prefixList.Add(new WebRequestPrefixElement("file", new FileWebRequestCreator())); // [2]
                      }
                      s_PrefixList = prefixList;
                  }
           }
      }
      return s_PrefixList;
 }
 set
 {
       s_PrefixList = value;
 }
}

PrefixList属性的Get访问器中,进行了一系列的判断以及初始化工作,其中最重要的工作则是在其内部自动添加了三个元素,均为WebRequestPrefixElement对象,而通过该对象,在prefixList中建立了Uri和IWebRequestCreate之间的关系:
prefixList.Add(new WebRequestPrefixElement("https", Creator)); // [0]
prefixList.Add(new WebRequestPrefixElement("http", Creator));  // [1]
prefixList.Add(new WebRequestPrefixElement("file", new FileWebRequestCreator())); // [2]

前两个对象中,工厂类型均为HttpWebRequestCreator,而第三个对象则为FileWebRequestCreator。这正好是.Net提供的两种继承WebRequest的具体工厂类。

调用WebRequest的静态方法Create()时,系统会根据传入的Uri对象,在prefixList中搜索前缀与PrefixList列表中的uri匹配的WebRequestPrefixElement类型对象(通过String.Compare()方法)。如果找到,则根据一一映射的关系,去调用对应的工厂类,创建相应的Web请求实例,即Create()方法中的第③行代码。

再回过头来看创建WebRequest对象的代码:
WebRequest myRequest = WebRequest.Create(“http://www.cnblogs.com”);

根据前面分析的过程,这行代码封装的内部实现应该是如下步骤:
1、将字符串”http://www.cnblogs.com”传递到WebRequest类的静态私有方法Create()中;
2、将WebRequest的私有属性PrefixList值赋给方法内的局部变量prefixList对象。此时会调用PrefixList的Get访问器。该访问器会初始化PrefixList对象,将默认的Uri和IWebRequestCreate类型的值添加到PrefixList中;
3、解析传入的Uri,得到值”http”,对应的IWebRequestCreate对象为HttpWebRequestCreator对象;
4、调用HttpWebRequestCreator对象的Create()方法,创建HttpWebRequest对象,并返回。

执行步骤的时序图如下:

factory6.GIF

现在,考虑扩展的情况。如果此时派生于WebRequest类的不仅仅有HttpWebRequest和FileWebRequest子类,还有新增加的其他子类,例如FtpRequest类,它对应的Uri为”ftp”。对应的工厂类为FtpWebRequestCreator。此时应该如何创建?根据前面的分析来看,由于PrefixList的Get访问器中,并没有添加其他的WebRequestPrefixElement对象,如果输入Uri前缀”ftp”,Create()方法是无法找到合适的IWebRequestCreate工厂对象的。难道,当我们扩展新的WebRequest子类时,还是需要修改WebRequest类中的Prefix属性代码吗?如果真是这样,前面引入的工厂类,以及许多设计就显得拙劣可笑了。

显然,这种可笑的错误,.Net Framework的设计师们是不可能犯下的。事实上,.Net在WebRequest的创建过程中,引入了WebRequestPrefixElement类以及ArrayList对象PrefixList,就已经提示我们,Framework已经考虑到了扩展的可能。因为,ArrayList对象是允许我们动态加入对象的。事实正是如此,在WebRequest类中,还提供了一个注册WebRequestPrefixElement的公共静态方法RegisterPrefix():
public static bool RegisterPrefix(string prefix, IWebRequestCreate creator)
{
 bool Error = false;
 int i;
 WebRequestPrefixElement Current;

 if (prefix == null)
 {
      throw new ArgumentNullException("prefix");
 }
 if (creator == null)
 {
      throw new ArgumentNullException("creator");
 }          

 lock(typeof(WebRequest))
 {            
      ArrayList prefixList = (ArrayList) PrefixList.Clone();
      i = 0;              
      while (i < prefixList.Count)
  {
           Current = (WebRequestPrefixElement)prefixList[i];
           if (prefix.Length > Current.Prefix.Length)
   {
                 // It is. Break out of the loop here.
                 break;
           }
           if (prefix.Length == Current.Prefix.Length)
   {
                 // They're the same length.
                 if (String.Compare(Current.Prefix, prefix, true, CultureInfo.InvariantCulture) == 0)
    {
                       // ...and the strings are identical. This is an error.
                       Error = true;
                       break;
                 }
          }
            i++;
      }
      if (!Error)
  {
           prefixList.Insert(i, new WebRequestPrefixElement(prefix, creator));
           PrefixList = prefixList;
      }
 }
 return !Error;
}

只要在已有的PrefixList中没有找到参数中传递进来的prefix值,就将新的prefix值和IWebRequestCreate对象插入到PrefixList中。通过该方法,就可以动态添加新的工厂类了。例如:
WebRequest.RegisterPrefix(“ftp”,new FtpWebRequestCreator());
WebRequest ftpRequest = WebRequest.Create(“ftp://www.cnblogs.com”);

通过这种实现方式,就可以解开具体工厂类、具体产品类与WebRequest静态方法Create()之间的耦合。

在.Net Framework中,关于WebRequest类对象的创建,其实现方式虽然看似复杂,但其本质仍然属于Factory Method模式。但为了类库的更易于使用,并考虑到通用性和扩展性,又引入了类似于映射的WebRequestPrefixElement类以及ArrayList对象,同时又将具体工厂类对象设定为internal,并包装到抽象产品基类WebRequest的静态方法中。这种设计方法是我们在应用Factory Method模式设计自己的类库时,值得借鉴的。

posted @ 2005-08-15 11:41  张逸  阅读(7002)  评论(15编辑  收藏  举报