设计模式——工厂方法模式
shanzm 人气:2
[TOC]
### 1. 简介 **工厂方法模式(Factory Method Pattern)**也称为**工厂模式**,又称为虚拟构造器模式或多态模式。 在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。 工厂方法模式主要类: * **Product**抽象产品类(或是接口),派生出所有的具体产品类`ConcreteProductA`、`ConcreteProductA` …… * **ConcreteProduct** 具体产品类,继承于`Product`抽象类 * **Factory** 抽象工厂接口,所有的具体工厂类都是实现该接口 * **ConcreteFactory** 具体工厂,实现了`Factory`接口,创建具体的产品对象 ![shanzm_FactoryMethodPattern_UML](https://img2020.cnblogs.com/blog/1576687/202004/1576687-20200403222802688-977958749.png)
### 2. 示例1-计算器重构 #### 2.1 背景说明 在[设计模式——简单工厂模式](https://www.cnblogs.com/shanzhiming/p/12616423.html)一文中,使用简单工厂模式创建了一个简单的四则计算器, 我们需要一个抽象的Operation父类,派生出四个加减乘除子类 在工厂中根据传入的符号参数,生成不同的运行类。 但是问题也就来了,若是添加一个新的运行类,我们依旧可以继承于Operation抽象父类,override运算方法,然后在工厂中添加一个新的分支,创建该运算类。 那么问题就来了!按照设计原则——开闭原则,我们应该开放扩展,关闭修改。在这里,我们扩展了运算方法,但是同时也对Factory类进行了修改,这明显不符合开闭原则啊! 其实这就是简单工厂模式的缺点。 为了解决这个缺点,我们引入工厂方法模式。(事实上,简单工厂模式是工厂方法模式的简化版,而不是因为简单工厂模式有缺点才演变为工厂方法模式的) 我们对在简单工厂模式中实现的计算器进行进一步的重构 #### 2.2 代码重构 提取出工厂接口:`IFactory`,分别创建每个运行类的工厂类`AddFactory`,`SubFactory`,`MulFactory`,`DivFactory`。 ```cs //工厂接口:抽象工厂 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(); } } ``` 这时我们在客户端,可以这样使用: ```cs 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 程序类图 ![shanzm_计算器5.0](https://img2020.cnblogs.com/blog/1576687/202004/1576687-20200403222840668-404988428.png)
### 3. 示例2-模拟多功能日记记录器 #### 3.1 背景说明 示例来源于《设计模式实训-第二版》 某系统日志记录器要求支持多种日志记录方式,如文件日志记录(FileLog)、数据库日志记录(DatabaseLog)等,且用户可以根据要求动态选择日志记录方式,现使用工厂方法模式设计该系统。 **这里我为什么要用这个示例?** 因为在实际开发中,一些框架和程序包都是按照工厂方法模式开发的,包括日志框架。 其实你想一想,使用的一些框架和程序包的调用,是不是都先是创建一个ConcreteFactory对象(或者Creator对象),之后使用该具体工厂对象创建ConcreteProduct对象 比如:[Quartz .NET任务调度框架](https://www.cnblogs.com/shanzhiming/p/12570677.html) #### 3.2 代码实现 **①抽象产品和具体产品的实现代码** ```cs //抽象产品:日志记录器总接口(使用抽象类也可以) public interface ILog { void WriteLog(); } //具体产品:文件日志记录器 public class FileLog : ILog { public void WriteLog() { Console.WriteLine("记录日志于日志文件中"); } } //具体产品:数据库日志记录器 public class DatabaseLog : ILog { public void WriteLog() { Console.WriteLine("记录日志于日志数据库中"); } } ``` **②抽象工厂和具体工厂的实现代码** ```cs //抽象工厂:日志记录器工厂 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(); } } ``` **③客户端代码** ```cs 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 程序类图 ![](https://img2020.cnblogs.com/blog/1576687/202004/1576687-20200403222921719-842678899.png)
### 4. 总结分析 #### 4.1 优点 * 当调用者需要一个具体产品对象,只要使用该具体产品的具体工厂创建该对象即可。调用者不需要知道具体产品对象是怎么创建的 * 便于扩展:使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。 * 工厂方法模式是**平行的类层次结构** * 什么是平行的类层次结构?简单的说,假如有两个类的层次,其中一个层次中的类在另外一个层次中都有相对应的类,则称这两个类层次是平行的类层次结构。工厂方法模式就是平行的类层次结构。这里可以看UML图,非常明显!工厂类层次和产品类层次就是平行的。 * 这种平行的类层次结构用来干什么呢?主要用来把一个类层次中的某些行为分离出来,让类层次中的类把原本属于自己的职责,委托给分离出来的类去实现,从而使得类层次本身变得更简单,更容易扩展和复用。 #### 4.2 缺点 * 扩展系统的时候,一旦添加一个具体产品类,则必须同时添加一个相应的具体工厂类。所以系统中类的添加是成对的添加,一定程度上造成系统繁杂。 #### 4.3 适应场合 * 工厂方法模式是new一个对象的替代品。所以其实在需要大量实例化对象的地方都是可以使用的。 * 实际中开发中,**工厂方法主要用于工具包和框架中** #### 4.4 其他说明 * 为什么工厂方法模式也称为**多态工厂模式**?工厂接口的有不同的实现(也就是多态),换一句说也就是具体工厂类都有同一个工厂接口。 * 工厂方法模式的简化:若是产品对象较少,可以使用一个具体工厂类(包含一个静态的工厂方法)根据参数去创建所有的具体产品,这就是所谓的[简单工厂模式](https://www.cnblogs.com/shanzhiming/p/12616423.html)。 * 注意简单工厂模式就是通过**参数化工厂方法**实现的。参数化工厂方法具体指:通过给工厂方法传递参数,让工厂方法根据参数创建不同的产品对象。 * 详细可参考《研磨设计模式》
### 5. 参考及源码下载 * [示例中源代码下载](https://github.com/shanzm/DesignPatterns) * [《大话设计模式》]() * [《设计模式实训教程-第二版》]() * [《研磨设计模式》]() * [《图说设计模式》](https:/https://img.qb5200.com/download-x/design-patterns.readthedocs.io/zh_CN/latest/index.html) * [设计模式——简单工厂模式](https://www.cnblogs.com/shanzhiming/p/12616423.html) * [设计模式——面向对象的设计原则](https://www.cnblogs.com/shanzhiming/p/12608123.html)
shanzm-2020年4月3日 22:26:27
### 1. 简介 **工厂方法模式(Factory Method Pattern)**也称为**工厂模式**,又称为虚拟构造器模式或多态模式。 在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。 工厂方法模式主要类: * **Product**抽象产品类(或是接口),派生出所有的具体产品类`ConcreteProductA`、`ConcreteProductA` …… * **ConcreteProduct** 具体产品类,继承于`Product`抽象类 * **Factory** 抽象工厂接口,所有的具体工厂类都是实现该接口 * **ConcreteFactory** 具体工厂,实现了`Factory`接口,创建具体的产品对象 ![shanzm_FactoryMethodPattern_UML](https://img2020.cnblogs.com/blog/1576687/202004/1576687-20200403222802688-977958749.png)
注:在GoF的《设计模式:可复用面向对象软件的基础》,对工厂方法模式的描述中使用的是 Creator接口,其含有 FactoryMethod()方法
### 2. 示例1-计算器重构 #### 2.1 背景说明 在[设计模式——简单工厂模式](https://www.cnblogs.com/shanzhiming/p/12616423.html)一文中,使用简单工厂模式创建了一个简单的四则计算器, 我们需要一个抽象的Operation父类,派生出四个加减乘除子类 在工厂中根据传入的符号参数,生成不同的运行类。 但是问题也就来了,若是添加一个新的运行类,我们依旧可以继承于Operation抽象父类,override运算方法,然后在工厂中添加一个新的分支,创建该运算类。 那么问题就来了!按照设计原则——开闭原则,我们应该开放扩展,关闭修改。在这里,我们扩展了运算方法,但是同时也对Factory类进行了修改,这明显不符合开闭原则啊! 其实这就是简单工厂模式的缺点。 为了解决这个缺点,我们引入工厂方法模式。(事实上,简单工厂模式是工厂方法模式的简化版,而不是因为简单工厂模式有缺点才演变为工厂方法模式的) 我们对在简单工厂模式中实现的计算器进行进一步的重构 #### 2.2 代码重构 提取出工厂接口:`IFactory`,分别创建每个运行类的工厂类`AddFactory`,`SubFactory`,`MulFactory`,`DivFactory`。 ```cs //工厂接口:抽象工厂 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(); } } ``` 这时我们在客户端,可以这样使用: ```cs 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 程序类图 ![shanzm_计算器5.0](https://img2020.cnblogs.com/blog/1576687/202004/1576687-20200403222840668-404988428.png)
注1:类图是vs自动生成的,但是VS中无法生成`依赖关系`的图示,所以我自己用画图软件给补上来,便于理解程序中各个类的关系。
注2:实现接口在vs中自动生成的类中是使用棒棒糖表示法
### 3. 示例2-模拟多功能日记记录器 #### 3.1 背景说明 示例来源于《设计模式实训-第二版》 某系统日志记录器要求支持多种日志记录方式,如文件日志记录(FileLog)、数据库日志记录(DatabaseLog)等,且用户可以根据要求动态选择日志记录方式,现使用工厂方法模式设计该系统。 **这里我为什么要用这个示例?** 因为在实际开发中,一些框架和程序包都是按照工厂方法模式开发的,包括日志框架。 其实你想一想,使用的一些框架和程序包的调用,是不是都先是创建一个ConcreteFactory对象(或者Creator对象),之后使用该具体工厂对象创建ConcreteProduct对象 比如:[Quartz .NET任务调度框架](https://www.cnblogs.com/shanzhiming/p/12570677.html) #### 3.2 代码实现 **①抽象产品和具体产品的实现代码** ```cs //抽象产品:日志记录器总接口(使用抽象类也可以) public interface ILog { void WriteLog(); } //具体产品:文件日志记录器 public class FileLog : ILog { public void WriteLog() { Console.WriteLine("记录日志于日志文件中"); } } //具体产品:数据库日志记录器 public class DatabaseLog : ILog { public void WriteLog() { Console.WriteLine("记录日志于日志数据库中"); } } ``` **②抽象工厂和具体工厂的实现代码** ```cs //抽象工厂:日志记录器工厂 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(); } } ``` **③客户端代码** ```cs 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 程序类图 ![](https://img2020.cnblogs.com/blog/1576687/202004/1576687-20200403222921719-842678899.png)
### 4. 总结分析 #### 4.1 优点 * 当调用者需要一个具体产品对象,只要使用该具体产品的具体工厂创建该对象即可。调用者不需要知道具体产品对象是怎么创建的 * 便于扩展:使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。 * 工厂方法模式是**平行的类层次结构** * 什么是平行的类层次结构?简单的说,假如有两个类的层次,其中一个层次中的类在另外一个层次中都有相对应的类,则称这两个类层次是平行的类层次结构。工厂方法模式就是平行的类层次结构。这里可以看UML图,非常明显!工厂类层次和产品类层次就是平行的。 * 这种平行的类层次结构用来干什么呢?主要用来把一个类层次中的某些行为分离出来,让类层次中的类把原本属于自己的职责,委托给分离出来的类去实现,从而使得类层次本身变得更简单,更容易扩展和复用。 #### 4.2 缺点 * 扩展系统的时候,一旦添加一个具体产品类,则必须同时添加一个相应的具体工厂类。所以系统中类的添加是成对的添加,一定程度上造成系统繁杂。 #### 4.3 适应场合 * 工厂方法模式是new一个对象的替代品。所以其实在需要大量实例化对象的地方都是可以使用的。 * 实际中开发中,**工厂方法主要用于工具包和框架中** #### 4.4 其他说明 * 为什么工厂方法模式也称为**多态工厂模式**?工厂接口的有不同的实现(也就是多态),换一句说也就是具体工厂类都有同一个工厂接口。 * 工厂方法模式的简化:若是产品对象较少,可以使用一个具体工厂类(包含一个静态的工厂方法)根据参数去创建所有的具体产品,这就是所谓的[简单工厂模式](https://www.cnblogs.com/shanzhiming/p/12616423.html)。 * 注意简单工厂模式就是通过**参数化工厂方法**实现的。参数化工厂方法具体指:通过给工厂方法传递参数,让工厂方法根据参数创建不同的产品对象。 * 详细可参考《研磨设计模式》
### 5. 参考及源码下载 * [示例中源代码下载](https://github.com/shanzm/DesignPatterns) * [《大话设计模式》]() * [《设计模式实训教程-第二版》]() * [《研磨设计模式》]() * [《图说设计模式》](https:/https://img.qb5200.com/download-x/design-patterns.readthedocs.io/zh_CN/latest/index.html) * [设计模式——简单工厂模式](https://www.cnblogs.com/shanzhiming/p/12616423.html) * [设计模式——面向对象的设计原则](https://www.cnblogs.com/shanzhiming/p/12608123.html)
加载全部内容