设计模式——工厂方法模式

目录1 简介2 示例1-计算器重构2 1 背景说明2 2 代码重构2 3 程序类图3 示例2-模拟多功能日记记录器3 1 背景说明3 2 代码实现3 3

shanzm-2020年4月3日 22:26:27

1. 简介

工厂方法模式(Factory Method Pattern)也称为工厂模式,又称为虚拟构造器模式或多态模式。

在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

工厂方法模式主要类:

  • Product抽象产品类(或是接口),派生出所有的具体产品类ConcreteProductAConcreteProductA ……

  • ConcreteProduct 具体产品类,继承于Product抽象类

  • Factory 抽象工厂接口,所有的具体工厂类都是实现该接口

  • ConcreteFactory 具体工厂,实现了Factory接口,创建具体的产品对象

shanzm_FactoryMethodPattern_UML

注:在GoF的《设计模式:可复用面向对象软件的基础》,对工厂方法模式的描述中使用的是 Creator接口,其含有 FactoryMethod()方法


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 程序类图

shanzm_计算器5.0

注1:类图是vs自动生成的,但是VS中无法生成`依赖关系`的图示,所以我自己用画图软件给补上来,便于理解程序中各个类的关系。
注2:实现接口在vs中自动生成的类中是使用棒棒糖表示法


3. 示例2-模拟多功能日记记录器

3.1 背景说明

示例来源于《设计模式实训-第二版》

某系统日志记录器要求支持多种日志记录方式,如文件日志记录(FileLog)、数据库日志记录(DatabaseLog)等,且用户可以根据要求动态选择日志记录方式,现使用工厂方法模式设计该系统。

这里我为什么要用这个示例? 因为在实际开发中,一些框架和程序包都是按照工厂方法模式开发的,包括日志框架。

其实你想一想,使用的一些框架和程序包的调用,是不是都先是创建一个ConcreteFactory对象(或者Creator对象),之后使用该具体工厂对象创建ConcreteProduct对象

比如:Quartz .NET任务调度框架

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 其他说明

  • 为什么工厂方法模式也称为多态工厂模式?工厂接口的有不同的实现(也就是多态),换一句说也就是具体工厂类都有同一个工厂接口。

  • 工厂方法模式的简化:若是产品对象较少,可以使用一个具体工厂类(包含一个静态的工厂方法)根据参数去创建所有的具体产品,这就是所谓的简单工厂模式

    • 注意简单工厂模式就是通过参数化工厂方法实现的。参数化工厂方法具体指:通过给工厂方法传递参数,让工厂方法根据参数创建不同的产品对象。

    • 详细可参考《研磨设计模式》



5. 参考及源码下载