设计模式——工厂方法模式
1. 简介
工厂方法模式(Factory Method Pattern)也称为工厂模式,又称为虚拟构造器模式或多态模式。
在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
工厂方法模式主要类:
-
Product抽象产品类(或是接口),派生出所有的具体产品类
ConcreteProductA
、ConcreteProductA
…… -
ConcreteProduct 具体产品类,继承于
Product
抽象类 -
Factory 抽象工厂接口,所有的具体工厂类都是实现该接口
-
ConcreteFactory 具体工厂,实现了
Factory
接口,创建具体的产品对象
2. 示例1-计算器重构
2.1 背景说明
在设计模式——简单工厂模式一文中,使用简单工厂模式创建了一个简单的四则计算器,
我们需要一个抽象的Operation父类,派生出四个加减乘除子类
在工厂中根据传入的符号参数,生成不同的运行类。
但是问题也就来了,若是添加一个新的运行类,我们依旧可以继承于Operation抽象父类,override运算方法,然后在工厂中添加一个新的分支,创建该运算类。
那么问题就来了!按照设计原则——开闭原则,我们应该开放扩展,关闭修改。在这里,我们扩展了运算方法,但是同时也对Factory类进行了修改,这明显不符合开闭原则啊!
其实这就是简单工厂模式的缺点。
为了解决这个缺点,我们引入工厂方法模式。(事实上,简单工厂模式是工厂方法模式的简化版,而不是因为简单工厂模式有缺点才演变为工厂方法模式的)
我们对在简单工厂模式中实现的计算器进行进一步的重构
2.2 代码重构
提取出工厂接口:IFactory
,分别创建每个运行类的工厂类AddFactory
,SubFactory
,MulFactory
,DivFactory
。
//工厂接口:抽象工厂
public interface IFactory
{
Operation CreateOperation();
}
//加法运行工厂:具体工厂
public class AddFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationAdd();
}
}
//减法运行工厂:具体工厂
public class SubFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationSub();
}
}
//乘法运行工厂:具体工厂
public class MulFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationMul();
}
}
//除法运行工厂:具体工厂
public class DivFactory : IFactory
{
public Operation CreateOperation()
{
return new OperationDiv();
}
}
这时我们在客户端,可以这样使用:
static void Main(string[] args)
{
//创建一个具体的工厂对象:加法工厂对象
IFactory addFactory = new AddFactory();
//使用加法工厂对象创建加法运算类
Operation addOper = addFactory.CreateOperation();
addOper.NumA = 2;
addOper.NumB = 3;
Console.WriteLine(addOper.GetResult());//print:5
Console.ReadKey();
}
其实到这里是可以发现,经过重构后的代码,每个具体运算类都有一个相应的工厂类
若是需要添加一个新的运行符,则我们只需要创建一个新的具体工厂类XXXFactory
,实现抽象工厂接口IFactory
,同时再创建新的运算符实现类XXX
,继承于运算抽象父类Operation
。这样就可以在不修改程序中的现有的类的情形下,实现对程序的扩展!满足了开闭原则,避免了简单工厂模式的缺点!
工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。你想要加功能,本来是改工厂类的,而现在是修改客户端
2.3 程序类图
3. 示例2-模拟多功能日记记录器
3.1 背景说明
示例来源于《设计模式实训-第二版》
某系统日志记录器要求支持多种日志记录方式,如文件日志记录(FileLog)、数据库日志记录(DatabaseLog)等,且用户可以根据要求动态选择日志记录方式,现使用工厂方法模式设计该系统。
这里我为什么要用这个示例? 因为在实际开发中,一些框架和程序包都是按照工厂方法模式开发的,包括日志框架。
其实你想一想,使用的一些框架和程序包的调用,是不是都先是创建一个ConcreteFactory对象(或者Creator对象),之后使用该具体工厂对象创建ConcreteProduct对象
3.2 代码实现
①抽象产品和具体产品的实现代码
//抽象产品:日志记录器总接口(使用抽象类也可以)
public interface ILog
{
void WriteLog();
}
//具体产品:文件日志记录器
public class FileLog : ILog
{
public void WriteLog()
{
Console.WriteLine("记录日志于日志文件中");
}
}
//具体产品:数据库日志记录器
public class DatabaseLog : ILog
{
public void WriteLog()
{
Console.WriteLine("记录日志于日志数据库中");
}
}
②抽象工厂和具体工厂的实现代码
//抽象工厂:日志记录器工厂
public interface ILogFactory
{
ILog CreateLog();
}
//具体工厂:文件日记记录器工厂
public class FileLogFactory : ILogFactory
{
public ILog CreateLog()
{
return new FileLog();
}
}
//具体工厂:数据库日记记录器工厂
public class DatabaseLogFactory : ILogFactory
{
public ILog CreateLog()
{
return new DatabaseLog();
}
}
③客户端代码
class Program
{
static void Main(string[] args)
{
//创建一个具体的工厂对象:FileLogFactory
ILogFactory logFac = new FileLogFactory();
//由工厂对象,创建产品对象:FileLog
//FileLog log = logFac.CreateLog() as FileLog;
ILog log = logFac.CreateLog();
log.WriteLog();//print:记日志于日志文件中
Console.ReadKey();
}
}
3.3 程序类图
4. 总结分析
4.1 优点
-
当调用者需要一个具体产品对象,只要使用该具体产品的具体工厂创建该对象即可。调用者不需要知道具体产品对象是怎么创建的
-
便于扩展:使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
-
工厂方法模式是平行的类层次结构
-
什么是平行的类层次结构?简单的说,假如有两个类的层次,其中一个层次中的类在另外一个层次中都有相对应的类,则称这两个类层次是平行的类层次结构。工厂方法模式就是平行的类层次结构。这里可以看UML图,非常明显!工厂类层次和产品类层次就是平行的。
-
这种平行的类层次结构用来干什么呢?主要用来把一个类层次中的某些行为分离出来,让类层次中的类把原本属于自己的职责,委托给分离出来的类去实现,从而使得类层次本身变得更简单,更容易扩展和复用。
-
4.2 缺点
- 扩展系统的时候,一旦添加一个具体产品类,则必须同时添加一个相应的具体工厂类。所以系统中类的添加是成对的添加,一定程度上造成系统繁杂。
4.3 适应场合
-
工厂方法模式是new一个对象的替代品。所以其实在需要大量实例化对象的地方都是可以使用的。
-
实际中开发中,工厂方法主要用于工具包和框架中
4.4 其他说明
-
为什么工厂方法模式也称为多态工厂模式?工厂接口的有不同的实现(也就是多态),换一句说也就是具体工厂类都有同一个工厂接口。
-
工厂方法模式的简化:若是产品对象较少,可以使用一个具体工厂类(包含一个静态的工厂方法)根据参数去创建所有的具体产品,这就是所谓的简单工厂模式。
-
注意简单工厂模式就是通过参数化工厂方法实现的。参数化工厂方法具体指:通过给工厂方法传递参数,让工厂方法根据参数创建不同的产品对象。
-
详细可参考《研磨设计模式》
-