解读设计模式----工厂方法模式(FactoryMethod Pattern)
概述:
Factory Method模式是应用最为广泛的设计模式,毕竟他负责了一系列对象的创建,而对象的创建正是面向对象编程中最为繁琐的行为。GOF在《设计模式》一书写到,“Factory Method模式使一个类的实例化延迟到子类。”准确的说,Factory Method模式是将创建对象实例的责任,转移到了工厂类中,并利用抽象原理,将实例化行为延迟到具体工厂类。
意图:
定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method模式使一个类的实例化延迟到子类。
UML图:
常见的例子:
动物--猫、狗、牛、羊、猪........是人都知道,动物它们都是有形状的吧,它们拥有各自不同的外观(Display),是动物他就一定会吃东西(大多数动物还能吃,会吃...)。那具体他们是什么形状的呢?它们用怎么样的方法去吃东西呢?这不是我们所需要关心的,我们所关心的应该是怎么通过工厂去创建具体动物的实例。
案例分析:
现在我们考虑一个日志记录的简单例子,假定我们要设计一个日志记录组件,支持记录的方法有记录到文本文件(TxtWrite)和记录到数据库(DbWrite)两种方式。在我门不考虑设计模式的情况下,这个日志组件的简单实现:
2using System.Collections.Generic;
3using System.Text;
4
5namespace DesignPattern.FactoryMethod
6{
7 class Program
8 {
9 static void Main(string[] args)
10 {
11 Resolve resolve = new Resolve("txt");
12 resolve.WriteLog("Log message");
13 }
14 }
15
16 class Log
17 {
18 public static void TxtWrite(string message)
19 {
20 Console.WriteLine("TxtWrite():" + message);
21 }
22
23 public static void DbWrite(string message)
24 {
25 Console.WriteLine("DbWrite():" + message);
26 }
27 }
28
29 class Resolve
30 {
31 private string _type;
32 public Resolve(string type)
33 {
34 this._type = type;
35 }
36
37 public void WriteLog(string message)
38 {
39 if (this._type == "txt")
40 {
41 Log.TxtWrite(message);
42 }
43 else
44 {
45 Log.DbWrite(message);
46 }
47 }
48 }
49}
50
很显然这是不符合我们的要求的,一担需求改变,需要增加新的日志记录方式,那就必须得在Log类里增加新的实现方法,而在Resolve类里也同样需要增加新的if(){}语句或是switch(){}语句来做判断处理,这样就引起了整个应用程序的不稳定。如果在深入分析,日志被记录到数据库和文本文件是两种不同的对象,这里有必要、应该的是把他门分开做为单独的对象来处理。
2{
3 public static void TxtWrite(string message)
4 {
5 Console.WriteLine("TxtWrite():" + message);
6 }
7
8 public static void DbWrite(string message)
9 {
10 Console.WriteLine("DbWrite():" + message);
11 }
12}
其实我们可以进一步的为这两重不同的对象抽象出一个共性的父类出来。那到底该怎么去抽象呢?我们在来看看下面这个示例,抽象出一个共性的父类AbstractLog(可把他当作是一个具体的"抽象"产品),结构图如下:
2using System.Collections.Generic;
3using System.Text;
4
5namespace DesignPattern.FactoryMethod
6{
7 /// <summary>
8 /// 抽象"产品"
9 /// </summary>
10 public abstract class AbstractLog
11 {
12 public abstract List<Log> GetLog();
13 public abstract bool InsertLog(Log log);
14 }
15}
此时DbLog和TxtLog分别继承父类AbstractLog,其定义如下:
2/// 将日志记录到数据库
3/// </summary>
4public class DbLog:AbstractLog
5{
6 public override List<Log> GetLog()
7 {
8 List<Log> list = new List<Log>();
9 list.Add(new Log("被记录到数据库的日志", DateTime.Now));
10 return list;
11 }
12
13 public override bool InsertLog(Log log)
14 {
15 //.实现略..将日志记录到数据库
16 return true;
17 }
18}
19-----------------------------------------------
20/// <summary>
21/// 将日志记录到文本文件
22/// </summary>
23public class TxtLog:AbstractLog
24{
25 /// <summary>
26 /// 获取日志
27 /// </summary>
28 public override List<Log> GetLog()
29 {
30 List<Log> list = new List<Log>();
31 list.Add(new Log("被记录到文本文件的日志", DateTime.Now));
32 return list;
33 }
34
35 /// <summary>
36 /// 插入日志
37 /// </summary>
38 public override bool InsertLog(Log log)
39 {
40 //.实现略..将日志记录到数据库
41 return true;
42 }
43}
我们这样做的好处在哪呢?其实仔细观察就会发现,如果我们现在需要增加把日志记录到XML文件去,那该怎么处理呢?其实只需要新添一个新类并继承于AbstractLog并实现其方法既可,这样既满足了类之间的层次关系,还更好的符合了面向对象设计中的单一职责原则,每一个类都只负责一件具体的事情。
到这里我们的设计可说是完美的了,根据多态原理,我们完全可以像下面这样来调用:
2al.GetLog();
3//
4AbstractLog al1 = new TxtLog();
5al1.GetLog();
6//.
从上面我们不难看出,如果需要改DbLog为TxtLog就必须得new TxtLog(),这样的做法使程序显得很死板,对象的创建不够灵活。此时就需要解耦具体的日志记录方式和应用程序。这就要引入Factory Method模式了,每一个日志记录的对象就是工厂所生成的产品,既然有两种记录方式,那就需要两个不同的工厂去生产了,由于每一个日志的对象所对应的工厂都是负责这个日志对象的创建工作,这里我们是不是又应该抽象出工厂的共性呢?显然答案是肯定有必要的,结构图如下:
代码如下:
2{
3 /// <summary>
4 /// 抽象Log工厂(Creator)
5 /// </summary>
6 public abstract class AbstractLogFactory
7 {
8 public abstract AbstractLog CreateLog();
9 }
10}
2{
3 public override AbstractLog CreateLog()
4 {
5 return new DbLog();
6 }
7}
8----------------------------------------------
9public class TxtLogFactory:AbstractLogFactory
10{
11 public override AbstractLog CreateLog()
12 {
13 return new TxtLog();
14 }
15}
通过工厂方法模式我们把上面那对象创建工作封装在了工厂中,此时似乎完成了整个Factory Method的过程。我们先看看客户断的调用,代码如下:
2{
3 AbstractLogFactory alf = new DbLogFactory();
4 AbstractLog al = alf.CreateLog();
5
6 //al.InsertLog(new Log("到数据库", DateTime.Now));
7 Console.WriteLine("日志内容:" + al.GetLog()[0].Message + " 日志记录时间:" + al.GetLog()[0].RecordTime);
8
9 AbstractLogFactory alf2 = new TxtLogFactory();
10 AbstractLog al2 = alf2.CreateLog();
11 Console.WriteLine("日志内容:" + al2.GetLog()[0].Message + "日志记录时间:" + al2.GetLog()[0].RecordTime);
12}
仔细观察上面调用代码就会发现,客户程序中,我们有效地避免了具体产品对象和应用程序之间的耦合,可是我们也看到,增加了具体工厂对象和应用程序之间的耦合。那这样究竟带来什么好处呢?在程序中AbstractLog对象的创建是频繁的,要创建具体的类型决定于AbstractLogFactory alf=new DbLogFactory()/TxtLogFactory();此时,我们仍然为具体工厂对象的创建而忧心冲冲。
当我们要改变日志记录方式的时候还是得修改此处的代码,这样显然也不够灵活。此时,我们可以利用.NET的特性,我们可以避免这种不必要的修改。下面我们利用.NET中的反射机制来进一步修改我们的程序,这时就要用到配置文件了,如果我们想使用哪一种日志记录方式,则在相应的配置文件中设置如下:
2<configuration>
3 <appSettings>
4 <add key="factoryName" value="DbLogFactory"/>
5 </appSettings>
6</configuration>
通过上面配置文件和反射机制对工厂模式的扩展,这样创建具体工厂就变得更加灵活了。要创建指定的工厂就不在依赖与代码不需要在去修改调用端的代码实现了,只需要修改配置文件的配置参数既可。如果现在需要增加新的日志记录方法,我们就只需要增加一个新的类去实现AbstractLog和一个新的日志记录方法的工厂去实现AbstractLogFactory既可,我们不在需要去修改具体的工厂类。这样很好的符合了开放封闭原则。通过改善过后的客户端调用:
2{
3 string factoryName = ConfigurationManager.AppSettings["factoryName"];
4 string assemblyName="DesignPattern.FactoryMethod";
5 string className="DesignPattern.FactoryMethod."+factoryName;
6 AbstractLogFactory alf = (AbstractLogFactory)Assembly.Load(assemblyName).CreateInstance(className);
7 AbstractLog al = alf.CreateLog();
8 Console.WriteLine("日志内容:" + al.GetLog()[0].Message + " 日志记录时间:" + al.GetLog()[0].RecordTime);
9}
上面的的调用方式还是显得有点罗嗦,其实我们完全可以把具体工厂对象的创建工作封装到一个类里去,在此就不作详细解说了。
工厂方法模式总结:
Factory Method模式是设计模式中应用最为广泛的模式,通过本文,相信大家已经对它有了一定的认识。然而我们要明确的是:在面向对象的编程中,对象的创建工作非常简单,对象的创建时机却很重要。Factory Method要解决的就是对象的创建时机问题,它提供了一种扩展的策略,很好地符合了开放封闭原则。______________________________________________________________________________________________
本文示例代码下载:DesignPattern.FactoryMethod.rar
本文参考资料:
GOF----《设计模式》
Bruce Zhang----《软件设计精要与模式》
http://www.dofactory.com/