1、引言
简单工厂模式的一个缺点是:简单工厂模式系统难以扩展,一旦添加新产品就不得不修改简单工厂方法,这样就会造成简单工厂的实现逻辑过于复杂。而工厂方法模式正好可以解决这个问题。
2、概念
工厂方法模式定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
3、UML图
工厂方法模式的主要角色如下:
抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
继续使用简单工厂模式中点菜的例子,其UML图如下:
从UML图中可以看出,在工厂方法模式中,工厂类与具体产品类具有平行的等级结构,它们之间是一一对应的。针对UML图的解释如下:
Creator类:充当抽象工厂角色,任何具体工厂都必须继承该抽象类
TomatoScrambledEggsFactory和ShreddedPorkWithPotatoesFactory类:充当具体工厂角色,用来创建具体产品
Food类:充当抽象产品角色,具体产品的抽象类。任何具体产品都应该继承该类
TomatoScrambledEggs和ShreddedPorkWithPotatoes类:充当具体产品角色,实现抽象产品类对定义的抽象方法,由具体工厂类创建,它们之间有一一对应的关系。
3、应用场景
①客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
②创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
③客户不关心创建产品的细节,只关心产品的品牌。
4、实现思路
工厂方法模式之所以可以解决简单工厂的模式,是因为它的实现把具体产品的创建推迟到子类中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式就可以允许系统不修改工厂类逻辑的情况下来添加新产品,这样也就克服了简单工厂模式中缺点。
5、实现代码
这里还是以简单工厂模式中点菜的例子来实现,代码如下:
namespace 设计模式之工厂方法模式 { // 菜抽象类 public abstract class Food { // 输出点了什么菜 public abstract void Print(); } // 西红柿炒鸡蛋这道菜 public class TomatoScrambledEggs : Food { public override void Print() { Console.WriteLine("西红柿炒蛋好了!"); } } // 土豆肉丝这道菜 public class ShreddedPorkWithPotatoes : Food { public override void Print() { Console.WriteLine("土豆肉丝好了"); } } // 抽象工厂类 public abstract class Creator { // 工厂方法 public abstract Food CreateFoddFactory(); } // 西红柿炒蛋工厂类 public class TomatoScrambledEggsFactory:Creator { // 负责创建西红柿炒蛋这道菜 public override Food CreateFoddFactory() { return new TomatoScrambledEggs(); } } // 土豆肉丝工厂类 public class ShreddedPorkWithPotatoesFactory:Creator { // 负责创建土豆肉丝这道菜 public override Food CreateFoddFactory() { return new ShreddedPorkWithPotatoes(); } } // 客户端调用 class Client { static void Main(string[] args) { // 初始化做菜的两个工厂() Creator shreddedPorkWithPotatoesFactory = new ShreddedPorkWithPotatoesFactory(); Creator tomatoScrambledEggsFactory = new TomatoScrambledEggsFactory(); // 开始做西红柿炒蛋 Food tomatoScrambleEggs = tomatoScrambledEggsFactory.CreateFoddFactory(); tomatoScrambleEggs.Print(); //开始做土豆肉丝 Food shreddedPorkWithPotatoes = shreddedPorkWithPotatoesFactory.CreateFoddFactory(); shreddedPorkWithPotatoes.Print(); Console.Read(); } } }
使用工厂方法实现的系统,如果系统需要添加新产品时,我们可以利用多态性来完成系统的扩展,对于抽象工厂类和具体工厂中的代码都不需要做任何改动。例如,我们我们还想点一个“肉末茄子”,此时我们只需要定义一个肉末茄子具体工厂类和肉末茄子类就可以。而不用像简单工厂模式中那样去修改工厂类中的实现(具体指添加case语句)。具体代码为:
// 肉末茄子这道菜 public class MincedMeatEggplant : Food { // 重写抽象类中的方法 public override void Print() { Console.WriteLine("肉末茄子好了"); } } // 肉末茄子工厂类,负责创建肉末茄子这道菜 public class MincedMeatEggplantFactory : Creator { // 负责创建肉末茄子这道菜 public override Food CreateFoddFactory() { return new MincedMeatEggplant(); } } // 客户端调用 class Client { static void Main(string[] args) { // 如果客户又想点肉末茄子了 // 再另外初始化一个肉末茄子工厂 Creator minceMeatEggplantFactor = new MincedMeatEggplantFactory(); // 利用肉末茄子工厂来创建肉末茄子这道菜 Food minceMeatEggplant = minceMeatEggplantFactor.CreateFoddFactory(); minceMeatEggplant.Print(); Console.Read(); } }
6、优缺点
优点:克服了简单工厂违背开放-封闭原则的缺点,又保留了封装对象创建过程的优点,降低客户端和工厂的耦合性,所以说“工厂模式”是“简单工厂模式”的进一步抽象和推广。
缺点:每增加一个产品,相应的也要增加一个子工厂,加大了额外的开发量。
7、简单工厂 VS 工厂方法
简单工厂模式最大的优点在于工厂类中,包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。就像之前使用简单工厂模式设计的计算器代码,客户端不用管该用哪个类的实例,只需要把相应的运算符号给工厂,工厂自动就给出了相应的实例,客户端只需要去做运算就可以了,不同的实例会实现不同的运算。当问题也就在这里,如果要加一个“求 M 数的 N 次方” 的功能,我们是一定需要给简单工厂类的方法里加分支条件的,这就等于说,我们不仅对扩展开发了,也对修改开放了,这样就违背了开-闭原则。而且如果简单工厂类里与创建对象相关的代码太多,也会导致耦合性高。
工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断转移到了客户端代码来进行。你想要加功能,本来是改工厂类的,而现在是修改客户端。而且各个不同功能的实例对象的创建代码,也没有耦合在同一个工厂类里,这也是工厂方法模式对简单工厂模式解耦的一个体现。工厂方法模式克服了简单工厂会违背开-闭原则的缺点,又保持了封装对象创建过程的优点。所以它们都是集中封装了对象的创建,使得要更换对象时,不需要做大的改动就可以实现,降低了客户程序与产品对象的耦合。工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。但工厂方法模式的缺点是每增加一个产品类,就需要增加一个对应的工厂类,增加了额外的开发量。
8、.NET中实现了工厂方法的类
.NET 类库中也有很多实现了工厂方法的类,例如Asp.net中,处理程序对象是具体用来处理请求,当我们请求一个*.aspx的文件时,此时会映射到System.Web.UI.PageHandlerFactory类上进行处理,而对*.ashx的请求将映射到System.Web.UI.SimpleHandlerFactory类中(这两个类都是继承于IHttpHandlerFactory接口的),关于这点说明我们可以在“C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\Web.Config”文件中找到相关定义,具体定义如下:
下面我们就具体看下工厂方法模式在Asp.net中是如何实现的,如果对一个Index.aspx页面发出请求时,将会调用PageHandlerFactory中GetHandler方法来创建一个Index.aspx对象,它们之间的类图关系如下:
参考资料