工厂方法模式(Factory Method)
在软件系统中,经常面临着“某个对象”的创建工作,由于需求的变化,这个对象的具体实现经常面临着剧烈的变化,但是它却拥有比较稳定的接口。如何应对这种变化?提供一种封装机制来隔离出“这个易变对象”的变化,从而保持系统中“其它依赖该对象的对象”不随着需求的改变而改变?这就是要说的Factory Method模式了。 定义一个用户创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。 生活中的例子 工厂方法定义一个用于创建对象的接口,但是让子类决定实例化哪个类。压注成型演示了这种模式。塑料玩具制造商加工塑料粉,将塑料注入到希望形状的模具中。玩具的类别(车,人物等等)是由模具决定的。 在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不接触哪一个产品类被实例化这种细节。这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。在 Factory Method 模式中,工厂类与产品类往往具有平行的等级结构,它们之间一一对应。 现在我们考虑一个日志记录的例子(这里我们只是为了说明 Factory Method 模式,实际项目中的日志记录不会这么去做,也要比这复杂一些)。假定我们要设计日志记录的类,支持记录的方法有 FileLog 和 EventLog 两种方式。在这里我们先不谈设计模式,那么这个日志记录的类就很好实现了: 1 /// <summary> 2/// 日志记录类 3/// </summary> 4 public 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 }1617 public void Write(string LogType)18 { 19 switch(LogType.ToLower())20 { 21 case "event":22 WriteEvent();23 break;2425 case "file":26 WriteFile();27 break;2829 default:30 break;31 }32 }33 } 34 这样的程序结构显然不能符合我们的要求,如果我们增加一种新的日志记录的方式 DatabaseLog ,那就要修改 Log 类,随着记录方式的变化, switch 语句在不断的变化,这样就引起了整个应用程序的不稳定,进一步分析上面的代码,发现对于 EventLog 和 FileLog 是两种完全不同的记录方式,它们之间不应该存在必然的联系,而应该把它们分别作为单独的对象来对待。 1 /// <summary> 2/// EventLog类 3/// </summary> 4 public class EventLog 5 { 6 public void Write() 7 { 8 Console.WriteLine("EventLog Write Success!"); 9 }10} 11 12 /// <summary>13/// FileLog类14/// </summary> 15 public class FileLog 16 { 17 public void Write()18 { 19 Console.WriteLine("FileLog Write Success!");20 }21} 22 进一步抽象,为它们抽象出一个共同的父类,结构图如下:
实现代码: 1 /// <summary>2/// Log类3/// </summary> 4 public abstract class Log 5 { 6 public abstract void Write();7} 8 此时EventLog
和FileLog类的代码应该如下: 1 /// <summary> 2/// EventLog类 3/// </summary> 4 public class EventLog:Log 5 { 6 public override void Write() 7 { 8 Console.WriteLine("EventLog Write Success!"); 9 }10} 11 /// <summary>12/// FileLog类13/// </summary> 14 public 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> 4 public class EventFactory 5 { 6 public EventLog Create() 7 { 8 return new EventLog(); 9 }10} 11 /// <summary>12/// FileFactory类13/// </summary> 14 public class FileFactory 15 { 16 public FileLog Create()17 { 18 return new FileLog();19 }20} 21 这两个工厂和具体的产品之间是平行的结构,并一一对应,并在它们的基础上抽象出一个公用的接口,结构图如下:
实现代码如下: 1 /// <summary>2/// LogFactory类3/// </summary> 4 public abstract class LogFactory 5 { 6 public abstract Log Create();7} 8 1 /// <summary> 2/// EventFactory类 3/// </summary> 4 public class EventFactory:LogFactory 5 { 6 public override EventLog Create() 7 { 8 return new EventLog(); 9 }10} 11 /// <summary>12/// FileFactory类13/// </summary> 14 public 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> 4 public class App 5 { 6 public static void Main(string[] args) 7 { 8 LogFactory factory = new EventFactory(); 910 Log log = factory.Create();1112 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 1 /// <summary> 2/// App类 3/// </summary> 4 public 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);1213 Log log = factory.Create();14 log.Write();15 }16} 17 现在我们看到,在引进新产品(日志记录方式)的情况下,我们并不需要去修改工厂类,而只是增加新的产品类和新的工厂类(注意:这是任何时候都不能避免的),这样很好的符合了开放封闭原则。
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 的实现:
1 internal 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 工厂:
1 public interface IHttpHandlerFactory 2 { 3 // Methods4 IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);5 void ReleaseHandler(IHttpHandler handler);6} 7 IHttpHandlerFactory.GetHandler 是一个工厂方法模式的典型例子,在这个应用中,各个角色的设置如下: 抽象工厂角色: IHttpHandlerFactory 具体工厂角色: PageHandlerFactory 具体产品角色: ASP.SamplePage_aspx 理解上面所说的之后,我们就可以去自定义工厂类来对特定的资源类型进行处理。第一步我们需要创建两个类去分别实现 IHttpHandlerFactory 和 IHttpHandler 这两个接口。 1 public 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.GetHandler1011 void IHttpHandlerFactory.ReleaseHandler(12 IHttpHandler handler) { /*no-op*/ }1314} // HttpHandlerFactoryImpl 15 16 public class HttpHandlerImpl:IHttpHandler { 1718 void IHttpHandler.ProcessRequest(HttpContext context) { 19 20 context.Response.Write("sample handler invoked");21 22 }//ProcessRequest2324 bool IHttpHandler.IsReusable { get { return false; } }2526} // 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是一个具体的类且它提供一个工厂方法的缺省实现。 3. 工厂的作用并不仅仅只是创建一个对象,它还可以做对象的初始化,参数的设置等。 1. 用工厂方法在一个类的内部创建对象通常比直接创建对象更灵活。 2. Factory Method 模式通过面向对象的手法,将所要创建的具体对象的创建工作延迟到了子类,从而提供了一种扩展的策略,较好的解决了这种紧耦合的关系。 1. 当一个类不知道它所必须创建的对象的类的时候。 2. 当一个类希望由它的子类来指定它所创建的对象的时候。 3. 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。 Factory Method 模式是设计模式中应用最为广泛的模式,通过本文,相信读者已经对它有了一定的认识。然而我们要明确的是:在面向对象的编程中,对象的创建工作非常简单,对象的创建时机却很重要。Factory Method要解决的就是对象的创建时机问题,它提供了一种扩展的策略,很好地符合了开放封闭原则。 __________________________________________________________________________________ MSDN :《Exploring the Factory Design Pattern》 《DesignPatternsExplained》 本文转自lihuijun51CTO博客,原文链接: http://blog.51cto.com/terrylee/67748 ,如需转载请自行联系原作者