NET设计模式 第二部分 创建型模式(4):工厂方法模式(Factory Method)

工厂方法模式(Factory Method)

——.NET设计模式系列之五

Terrylee,2004年1月2日

概述

在软件系统中,经常面临着“某个对象”的创建工作,由于需求的变化,这个对象的具体实现经常面临着剧烈的变化,但是它却拥有比较稳定的接口。如何应对这种变化?提供一种封装机制来隔离出“这个易变对象”的变化,从而保持系统中“其它依赖该对象的对象”不随着需求的改变而改变?这就是要说的Factory Method模式了。

意图

定义一个用户创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。

结构图

生活中的例子

生活中的例子

工厂方法定义一个用于创建对象的接口,但是让子类决定实例化哪个类。压注成型演示了这种模式。塑料玩具制造商加工塑料粉,将塑料注入到希望形状的模具中。玩具的类别(车,人物等等)是由模具决定的。

工厂方法解说

在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不接触哪一个产品类被实例化这种细节。这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。在Factory Method模式中,工厂类与产品类往往具有平行的等级结构,它们之间一一对应。

现在我们考虑一个日志记录的例子(这里我们只是为了说明Factory Method模式,实际项目中的日志记录不会这么去做,也要比这复杂一些)。假定我们要设计日志记录的类,支持记录的方法有FileLog和EventLog两种方式。在这里我们先不谈设计模式,那么这个日志记录的类就很好实现了:

1/// <summary>
 2/// 日志记录类
 3/// </summary>
 4public class Log
 5    {
 6
 7        public void WriteEvent()
 8        {
 9            Console.WriteLine("EventLog Success!");
10        }
11
12        public void WriteFile()
13        {
14            Console.WriteLine("FileLog Success!");
15        }
16
17        public void Write(string LogType)
18        {
19            switch(LogType.ToLower())
20            {
21                case "event":
22                    WriteEvent();
23                    break;
24
25                case "file":
26                    WriteFile();
27                    break;
28
29                default:
30                    break;
31            }
32        }
33    }
34

这样的程序结构显然不能符合我们的要求,如果我们增加一种新的日志记录的方式DatabaseLog,那就要修改Log类,随着记录方式的变化,switch语句在不断的变化,这样就引起了整个应用程序的不稳定,进一步分析上面的代码,发现对于EventLog和FileLog是两种完全不同的记录方式,它们之间不应该存在必然的联系,而应该把它们分别作为单独的对象来对待。

1/// <summary>
 2/// EventLog类
 3/// </summary>
 4public class EventLog
 5{
 6    public void Write()
 7    {
 8        Console.WriteLine("EventLog Write Success!");
 9    }
10}
11
12/// <summary>
13/// FileLog类
14/// </summary>
15public class FileLog
16{
17    public void Write()
18    {
19        Console.WriteLine("FileLog Write Success!");
20    }
21}
22

进一步抽象,为它们抽象出一个共同的父类,结构图如下:

实现代码:

1/// <summary>
2/// Log类
3/// </summary>
4public abstract class Log
5{
6    public abstract void Write();
7}
8

此时EventLog和FileLog类的代码应该如下:

1/// <summary>
 2/// EventLog类
 3/// </summary>
 4public class EventLog:Log
 5{
 6    public override void Write()
 7    {
 8        Console.WriteLine("EventLog Write Success!");
 9    }
10}
11/// <summary>
12/// FileLog类
13/// </summary>
14public class FileLog:Log
15{
16    public override void Write()
17    {
18        Console.WriteLine("FileLog Write Success!");
19    }
20}
21

此时我们再看增加新的记录日志方式DatabaseLog的时候,需要做哪些事情?只需要增加一个继承父类Log的子类来实现,而无需再去修改EventLog和FileLog类,这样的设计满足了类之间的层次关系,又很好的符合了面向对象设计中的单一职责原则,每一个类都只负责一件具体的事情。到这里似乎我们的设计很完美了,事实上我们还没有看客户程序如何去调用。 在应用程序中,我们要使用某一种日志记录方式,也许会用到如下这样的语句:

EventLog eventlog = new EventLog();

eventlog.Write();

当日志记录的方式从EventLog变化为FileLog,我们就得修改所有程序代码中出现上面语句的部分,这样的工作量是可想而知的。此时就需要解耦具体的日志记录方式和应用程序。这就要引入Factory Method模式了,每一个日志记录的对象就是工厂所生成的产品,既然有两种记录方式,那就需要两个不同的工厂去生产了,代码如下:

1/// <summary>
 2/// EventFactory类
 3/// </summary>
 4public class EventFactory
 5{
 6    public EventLog Create()
 7    {
 8        return new EventLog();
 9    }
10}
11/// <summary>
12/// FileFactory类
13/// </summary>
14public class FileFactory
15{
16    public FileLog Create()
17    {
18        return new FileLog();
19    }
20}
21

这两个工厂和具体的产品之间是平行的结构,并一一对应,并在它们的基础上抽象出一个公用的接口,结构图如下:

实现代码如下:

1/// <summary>
2/// LogFactory类
3/// </summary>
4public abstract class LogFactory
5{
6    public abstract Log Create();
7}
8

此时两个具体工厂的代码应该如下:

1/// <summary>
 2/// EventFactory类
 3/// </summary>
 4public class EventFactory:LogFactory
 5{
 6    public override EventLog Create()
 7    {
 8        return new EventLog();
 9    }
10}
11/// <summary>
12/// FileFactory类
13/// </summary>
14public class FileFactory:LogFactory
15{
16    public override FileLog Create()
17    {
18        return new FileLog();
19    }
20}
21

这样通过工厂方法模式我们把上面那对象创建工作封装在了工厂中,此时我们似乎完成了整个Factory Method的过程。这样达到了我们应用程序和具体日志记录对象之间解耦的目的了吗?看一下此时客户端程序代码:

 1/// <summary>
 2/// App类
 3/// </summary>
 4public class App
 5{
 6    public static void Main(string[] args)
 7    {
 8        LogFactory factory = new EventFactory();
 9
10        Log log = factory.Create();
11
12        log.Write();
13    }
14}
15

在客户程序中,我们有效地避免了具体产品对象和应用程序之间的耦合,可是我们也看到,增加了具体工厂对象和应用程序之间的耦合。那这样究竟带来什么好处呢?我们知道,在应用程序中,Log对象的创建是频繁的,在这里我们可以把

LogFactory factory = new EventFactory();

这句话放在一个类模块中,任何需要用到Log对象的地方仍然不变。要是换一种日志记录方式,只要修改一处为:

LogFactory factory = new FileFactory();

其余的任何地方我们都不需要去修改。有人会说那还是修改代码,其实在开发中我们很难避免修改,但是我们可以尽量做到只修改一处。

其实利用.NET的特性,我们可以避免这种不必要的修改。下面我们利用.NET中的反射机制来进一步修改我们的程序,这时就要用到配置文件了,如果我们想使用哪一种日志记录方式,则在相应的配置文件中设置如下:

1<appSettings>
2    <add key="factoryName" value="EventFactory"></add>
3</appSettings>
4

此时客户端代码如下:

/**//// <summary>
 2/// App类
 3/// </summary>
 4public class App
 5{
 6    public static void Main(string[] args)
 7    {
 8        string strfactoryName = ConfigurationSettings.AppSettings["factoryName"];
 9        
10        LogFactory factory;
11        factory = (LogFactory)Assembly.Load("FactoryMethod").CreateInstance("FactoryMethod." + strfactoryName);
12
13        Log log = factory.Create();
14        log.Write();
15    }
16}
17

1/// <summary>
 2/// App类
 3/// </summary>
 4public class App
 5{
 6    public static void Main(string[] args)
 7    {
 8        string strfactoryName = ConfigurationSettings.AppSettings["factoryName"];
 9
10        LogFactory factory;
11        factory = (LogFactory)Assembly.Load("FactoryMethod").CreateInstance("FactoryMethod." + strfactoryName);
12
13        Log log = factory.Create();
14        log.Write();
15    }
16}
17

现在我们看到,在引进新产品(日志记录方式)的情况下,我们并不需要去修改工厂类,而只是增加新的产品类和新的工厂类(注意:这是任何时候都不能避免的),这样很好的符合了开放封闭原则。

ASP.NET HTTP通道中的应用

Factory Method模式在ASP.NET HTTP通道中我们可以找到很多的例子。ASP.NET HTTP通道是System.Web命名空间下的一个类,WEB Server使用该类处理接收到的HTTP请求,并给客户端发送响应。HTTP通道主要的工作有Session管理,应用程序池管理,缓存管理,安全等。

System.Web.HttpApplicationFactory

HttpRuntime是HTTP通道的入口点,它根据每一个具体的请求创建一个HttpContext实例, HttpRuntime并没有确定它将要处理请求的HttpApplication对象的类型,它调用了一个静态的工厂方法HttpApplicationFactory.GetApplicationInstance,通过它来创建HttpContext实例。GetApplicationInstance使用HttpContext实例来确定针对这个请求该响应哪个虚拟路径,如果这个虚拟路径以前请求过,HttpApplication(或者一个继承于ASP.Global_asax的类的实例)将直接从应用程序池中返回,否则针对该虚拟路径将创建一个新的HttpApplication对象并返回。如下图所示:

HttpApplicationFactory.GetApplicationInstance带有一个类型为HttpContext的参数,创建的所有对象(产品)都是HttpApplication的类型,通过反编译,来看一下GetApplicationInstance的实现:

1internal static IHttpHandler GetApplicationInstance(HttpContext context)
 2{
 3      if (HttpApplicationFactory._customApplication != null)
 4      {
 5            return HttpApplicationFactory._customApplication;
 6      }
 7      if (HttpDebugHandler.IsDebuggingRequest(context))
 8      {
 9            return new HttpDebugHandler();
10      }
11      if (!HttpApplicationFactory._theApplicationFactory._inited)
12      {
13            lock (HttpApplicationFactory._theApplicationFactory)
14            {
15                  if (!HttpApplicationFactory._theApplicationFactory._inited)
16                  {
17                        HttpApplicationFactory._theApplicationFactory.Init(context);
18                        HttpApplicationFactory._theApplicationFactory._inited = true;
19                  }
20            }
21      }
22      return HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context);
23}
24

System.Web.IHttpHandlerFactory

我们来做进一步的探索,HttpApplication实例需要一个Handler对象来处理资源请求, HttpApplication的主要任务就是找到真正处理请求的类。HttpApplication首先确定了一个创建Handler对象的工厂,来看一下在Machine.config文件中的配置区<httphandlers>,在配置文件注册了应用程序的具体处理类。例如在Machine.config中对*.aspx的处理将映射到System.Web.UI.PageHandlerFactory 类,而对*.ashx的处理将映射到System.Web.UI.SimpleHandlerFactory 类,这两个类都是继承于IhttpHandlerFactory接口的具体类:

<httpHandlers>

<add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory" />

<add verb="*" path="*.ashx" type="System.Web.UI.SimpleHandlerFactory" />



</httpHandlers>

这个配置区建立了资源请求的类型和处理请求的类之间的一个映射集。如果一个.aspx页面发出了请求,将会调用System.Web.UI.PageHandlerFactory类,HttpApplication调用接口IHttpHandlerFactory中的工厂方法GetHandler来创建一个Handler对象。当一个名为sample.aspx的页面发出请求时,通过PageHandlerFactory将返回一个ASP.SamplePage_aspx对象(具体产品),如下图:

IHttpHandlerFactory工厂:

1public interface IHttpHandlerFactory
2{
3      // Methods
4      IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
5      void ReleaseHandler(IHttpHandler handler);
6}
7

IHttpHandlerFactory.GetHandler是一个工厂方法模式的典型例子,在这个应用中,各个角色的设置如下:

抽象工厂角色:IHttpHandlerFactory

具体工厂角色:PageHandlerFactory

抽象产品角色:IHttpHandler

具体产品角色:ASP.SamplePage_aspx

进一步去理解

理解上面所说的之后,我们就可以去自定义工厂类来对特定的资源类型进行处理。第一步我们需要创建两个类去分别实现IHttpHandlerFactory 和IHttpHandler这两个接口。

1public class HttpHandlerFactoryImpl:IHttpHandlerFactory {
 2
 3   IHttpHandler IHttpHandlerFactory.GetHandler(
 4      HttpContext context, String requestType,
 5      String url, String pathTranslated ) {
 6
 7         return new HttpHandlerImpl();
 8
 9   }//IHttpHandlerFactory.GetHandler
10
11   void IHttpHandlerFactory.ReleaseHandler(
12      IHttpHandler handler) { /*no-op*/ }
13
14}//HttpHandlerFactoryImpl
15
16public class HttpHandlerImpl:IHttpHandler {
17
18   void IHttpHandler.ProcessRequest(HttpContext context) {
19
20      context.Response.Write("sample handler invoked");
21
22   }//ProcessRequest
23
24   bool IHttpHandler.IsReusable { get { return false; } }
25
26}//HttpHandlerImpl
27

第二步需要在配置文件中建立资源请求类型和处理程序之间的映射。我们希望当请求的类型为*.sample时进入我们自定义的处理程序,如下:

<httpHandlers>

   <add verb="*" path="*.sample" 

      type="HttpHandlerFactoryImpl,SampleHandler" />

</httpHandlers>

最后一步我们需要把文件扩展*.sample映射到ASP.NET ISAPI扩展DLL(aspnet_isapi.dll)上。由于我们已经建立了用于处理新扩展文件的处理程序了,我们还需要把这个扩展名告诉IIS并把它映射到ASP.NET。如果你不执行这个步骤而试图访问*.sample文件,IIS将简单地返回该文件而不是把它传递给ASP.NET运行时。其结果是该HTTP处理程序不会被调用。

运行Internet服务管理器,右键点击默认Web站点,选择属性,移动到主目录选项页,并点击配置按钮。应用程序配置对话框弹出来了。点击添加按钮并在可执行字段输入aspnet_isapi.dll文件路径,在扩展字段输入.sample。其它字段不用处理;该对话框如下所示:

在.NET Framework中,关于工厂模式的使用有很多的例子,例如IEnumerable和IEnumerator就是一个Creator和一个Product;System.Security.Cryptography中关于加密算法的选择,SymmetricAlgorithm, AsymmetricAlgorithm, 和HashAlgorithm分别是三个工厂,他们各有一个静态的工厂方法Create;System.Net.WebRequest是 .NET Framework 的用于访问 Internet 数据的请求/响应模型的抽象基类。使用该请求/响应模型的应用程序可以用协议不可知的方式从 Internet 请求数据。在这种方式下,应用程序处理 WebRequest 类的实例,而协议特定的子类则执行请求的具体细节。请求从应用程序发送到某个特定的 URI,如服务器上的 Web 页。URI 从一个为应用程序注册的 WebRequest 子代列表中确定要创建的适当子类。注册 WebRequest 子代通常是为了处理某个特定的协议(如 HTTP 或 FTP),但是也可以注册它以处理对特定服务器或服务器上的路径的请求。有时间我会就.NET Framework中工厂模式的使用作一个专题总结。

实现要点

1.  Factory Method模式的两种情况:一是Creator类是一个抽象类且它不提供它所声明的工厂方法的实现;二是Creator是一个具体的类且它提供一个工厂方法的缺省实现。

2.  工厂方法是可以带参数的。

3.  工厂的作用并不仅仅只是创建一个对象,它还可以做对象的初始化,参数的设置等。

效果

1.  用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活。

2.  Factory Method模式通过面向对象的手法,将所要创建的具体对象的创建工作延迟到了子类,从而提供了一种扩展的策略,较好的解决了这种紧耦合的关系。

适用性

在以下情况下,适用于工厂方法模式:

  1. 当一个类不知道它所必须创建的对象的类的时候。
  2. 当一个类希望由它的子类来指定它所创建的对象的时候。
  3. 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

总结

Factory Method模式是设计模式中应用最为广泛的模式,通过本文,相信读者已经对它有了一定的认识。然而我们要明确的是:在面向对象的编程中,对象的创建工作非常简单,对象的创建时机却很重要。Factory Method要解决的就是对象的创建时机问题,它提供了一种扩展的策略,很好地符合了开放封闭原则。__________________________________________________________________________________

参考文献:

《设计模式》(中文版)

MSDN:《Exploring the Factory Design Pattern》

《DesignPatternsExplained》

时间: 2024-10-10 23:55:49

NET设计模式 第二部分 创建型模式(4):工厂方法模式(Factory Method)的相关文章

JAVA设计模式(01):创建型-工厂模式【工厂方法模式】(Factory Method)

简单工厂模式尽管简单,但存在一个非常严重的问题.当系统中须要引入新产品时,因为静态工厂方法通过所传入參数的不同来创建不同的产品,这必然要改动工厂类的源码,将违背"开闭原则".怎样实现添加新产品而不影响已有代码?工厂方法模式应运而生,本文将介绍另外一种工厂模式--工厂方法模式. 1 日志记录器的设计 Sunny软件公司欲开发一个系统执行日志记录器(Logger).该记录器能够通过多种途径保存系统的执行日志,如通过文件记录或数据库记录,用户能够通过改动配置文件灵活地更换日志记录方式. 在设

iOS设计模式——工厂方法(简单工厂模式,工厂方法模式, 抽象工厂模式)

1. 简单工厂模式 如何理解简单工厂,工厂方法, 抽象工厂三种设计模式? 简单工厂的生活场景,卖早点的小摊贩,他给你提供包子,馒头,地沟油烙的煎饼等,小贩是一个工厂,它生产包子,馒头,地沟油烙的煎饼.该场景对应的UML图如下所示: 图1:简单工厂模式UML图 简单工厂模式的参与者: 工厂(Factory)角色:接受客户端的请求,通过请求负责创建相应的产品对象. 抽象产品(Abstract Product)角色: 是工厂模式所创建对象的父类或是共同拥有的接口.可是抽象类或接口. 具体产品(Conc

iOS常用设计模式——工厂方法(简单工厂模式,工厂方法模式, 抽象工厂模式)

1. 简单工厂模式 如何理解简单工厂,工厂方法, 抽象工厂三种设计模式? 简单工厂方法包含:父类拥有共同基础接口,具体子类实现子类特殊功能,工厂类根据参数区分创建不同子类实例.该场景对应的UML图如下所示: 代码结构如下 Animal 类 @interface Animal :NSObject @proterty(nonatomic,strong) NSString *name; -(void)laugh; @end Dog类 @interface Dog:Animal @end Cat类 @i

iOS经常使用设计模式——工厂方法(简单工厂模式,工厂方法模式, 抽象工厂模式)

1. 简单工厂模式 怎样理解简单工厂,工厂方法. 抽象工厂三种设计模式? 简单工厂的生活场景.卖早点的小摊贩.他给你提供包子,馒头,地沟油烙的煎饼等,小贩是一个工厂.它生产包子,馒头,地沟油烙的煎饼. 该场景相应的UML图例如以下所看到的: 图1:简单工厂模式UML图 简单工厂模式的參与者: 工厂(Factory)角色:接受client的请求,通过请求负责创建对应的产品对象. 抽象产品(Abstract Product)角色:是工厂模式所创建对象的父类或是共有的接口.但是抽象类或接口. 详细产品

Java设计模式学习记录-简单工厂模式、工厂方法模式

前言 之前介绍了设计模式的原则和分类等概述.今天开启设计模式的学习,首先要介绍的就是工厂模式,在介绍工厂模式前会先介绍一下简单工厂模式,这样由浅入深来介绍. 简单工厂模式 做法:创建一个工厂(方法或类)用来制造对象. 当一个人想要用手机的时候,需要自己创建手机然后来使用. 如下: public class IphoneX { public IphoneX(){ System.out.println("##### 制造iphoneX #####"); } } public class I

创建模式之工厂方法模式

视频录制地址:https://www.bilibili.com/video/BV1s7411X7cJ/  (https://member.bilibili.com/v2#/upload-manager/article) (因为视频暂时还在审核.所以提供后台地址) 一.简单工厂模式简述 简单工厂模式定义如下: 简单工厂模式(Simple Factory Pattern):定义一个工厂类,它可以根据参数的不同返回不同类的实例,被创 建的实例通常都具有共同的父类.因为在简单工厂模式中用于创建实例的方法

设计模式- 简单工厂模式、工厂方法模式及其比较

简单工厂模式及实例 前言工大有许多同学是做java的,大家都知道java最大的优点是它的完全OO化和它在多年的发展过程中吸收和总结了许多先进的框架与模式,其中工厂模式就是最常用的模式之一.下面我想将我在学习和实践过程中对工厂模式的认识与了解介绍给大家.由于笔者能力限制,在实践中也没参与过什么大的项目,笔者参与过的项目用到的工厂模式主要是简单工厂模式(Simple Factory)和工厂方法模式(Factory Method),所以笔者在本文主要介绍的是这两种模式.准备知识在OO设计领域,我们知道

设计模式之_简单工厂模式、工厂方法模式、抽象工厂模式 、策略模式、策略与工厂的区别(转)

一.前言 话说十年前,有一个爆发户,他家有三辆汽车(Benz(奔驰).Bmw(宝马).Audi(奥迪)),还雇了司机为他开车.不过,爆发户坐车时总是这样:上Benz车后跟司机说“开奔驰车!”,坐上Bmw后他说“开宝马车!”,坐上 Audi后他说“开奥迪车!”.你一定说:这人有病!直接说开车不就行了?!而当把这个爆发户的行为放到我们程序语言中来,我们发现C语言一直是通过这种方式来坐车的 幸运的是这种有病的现象在OO语言中可以避免了.下面以Java语言为基础来引入我们本文的主题:工厂模式! 二.简介

设计模式之工厂模式与工厂方法模式

工厂模式类图 简单工厂模式代码 package pattern.factory; /** * @ClassName IShape * @Description TODO:描述该接口职责 * @Author ckmike * @Date 18-12-14 上午11:33 * @Version 1.0 * @Copyright ckmike **/ public interface IShape { void draw(); double area(); } package pattern.fact

简单工厂模式,工厂方法模式,抽象工厂模式

简单工厂模式.抽象工厂模式.工厂方法模式,这三种工厂模式都属于设计模式中的创建型模式,它们在形式和特点上也多少有些相似,其最终目的都是帮我们将对象的实例化部分取出来,进而优化系统架构,增强系统的扩展性,也就是说更好的体现开放封闭原则. 简单工厂模式: 概念: 简单工厂模式是类的创建模式,又叫做静态工厂方法模式,是由一个工厂类根据传入的参量决定创建出哪一种产品类的实例,涉及到工厂角色.抽象产品角色以及具体产品角色. 结构图: 分析: 一: 简单工厂类是整个模式的关键所在,包含了必要的逻辑判断,根据