工厂方法模式

本篇文章会先介绍 工厂方法模式​ 的一些知识和特点,并在最后对 工厂方法模式简单工厂模式 这二者之间的异同做一些分析

意图

先看一下 GOF 中是如何对 工厂方式模式 进行定义的:

定义一个用于创建对象的接口,让子类决定实例化哪一个类。 工厂方法模式 使一个类的实例化延迟到其子类

所谓使一个类的实例化延迟到其子类,是因为抽象的 (工厂) 类不知道要实例化哪个具体 (产品) 类,所以实例化动作只能由具体的子 (工厂) 类去做, 抽象和具体 (工厂) 类中定义的、产生实例 (产品) 的接口函数,被我们成为 工厂方法,因为它负责 “生产” 一个对象

工厂方法模式 简单点说就是解决接口没法直接 new 的问题, 而 GOF 中对其使用场景的描述,也举了一些例子:

  • 当一个类不知道它所必须创建的对象的类的时候
  • 当一个类希望由它的子类来指定它所创建的对象的时候
  • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候

关于使用场景的第三点,我觉得这样解释可能更简单一点,具体的工厂类在创建实例的时候,具体创建哪个产品对象,这种一对一的信息,对抽象工厂类而言并没有必要知道,这些信息只是具体工厂类自己维护的局部信息,换句话说,我只要产品就行,工厂你怎么生产我不关心,生产的对象是叫 A 还是叫 B 我也不关心的, 只要你有我要的产品功能就行

说了这么多概念的东西,还是看一下 UML 图 (图片来自于《大话设计模式》) 对这个代码结构有个大致的了解

UML

所以 工厂方法模式 的核心是下面几个点

  • Creator(抽象工厂):声明一个工厂方法,该方法返回一个 Product 类型的对象
  • ConcreteCreator(具体工厂):重载工厂方法,返回和自己一一对应的具体产品实例
  • Product(抽象产品):抽象产品的一些共同接口
  • ConcreteProduct(具体产品):具体产品
  • 一个具体工厂对应一个具体产品

优缺点

根据 UML 图可以得到以下几个有点:

  • 隐藏了构造的过程,通过具体工厂的接口直接就能获取产品对象,很好的对外层屏蔽了代码的复杂度
  • 外层使用抽象的产品类,不关心具体的产品是什么,依赖于抽象,一定程度上减少了耦合
  • 一个工厂对应一个产品,所以当我们需要添加新的产品时,只需要添加一个新的工厂和产品即可,在改动上满足符合开放-封闭原则

当然也有缺陷:

  • 每新增一个产品,就需要增加一个对应的产品的具体工厂类,增加了代码的冗余度
  • 一个工厂只能生产一个产品
  • 当我们仅仅需要产品对象时,迫使创建工厂的子类

怎么写

还是以 简单工厂模式 里提到的加减乘除为例子

操作类的代码实现没变:

class Operation
{
public:
    Operation():A(0), B(0){}
    virtual ~Operation(){}

    double GetA() const { return A; }
    double GetB() const { return B; }
    void SetA(double x) { A=x; }
    void SetB(double y) { B=y; }
    double virtual GetResult(){ return 0; }

private:
    double A, B;
};

class Add : public Operation
{
public:
    double GetResult()
    {
        return GetA()+GetB();
    }
};

class Sub : public Operation
{
public:
    double GetResult()
    {
        return GetA()-GetB();
    }
};

class Mul : public Operation
{
public:
    double GetResult()
    {
        return GetA()*GetB();
    }
};

class Div : public Operation
{
public:
    double GetResult()
    {
        if (GetB() == 0)
            throw string("Division by zero condition!");
        return GetA()/GetB();
    }
};

工厂类的实现如下:

class Factory
{
public:
    virtual Operation *CreateOperator()=0;
};

class AddFactory : public Factory
{
public:
    Operation *CreateOperator() {
        return new Add();
    }
};

class SubFactory : public Factory
{
public:
    Operation *CreateOperator() {
        return new Sub();
    }
};

class MulFactory : public Factory
{
public:
    Operation *CreateOperator() {
        return new Mul();
    }
};

class DivFactory : public Factory
{
public:
    Operation *CreateOperator() {
        return new Div();
    }
};

main 函数调用的时候代码如下:

#define SAFE_DELETE(p) { if(p) { delete p; p=nullptr;}}
int main()
{
    double A = 20;
    double B = 0;

    Factory *divFactory = new DivFactory();
    Operation *op = divFactory->CreateOperator();
    op->SetA(A);
    op->SetB(B);
    try {
        cout<< op->GetResult() <<endl;
    } catch (string exception ) {
        cout<< exception <<endl;
    }

    SAFE_DELETE(op);
    SAFE_DELETE(divFactory)
}

工厂方法模式简单工厂模式 的比较

在网上很多资料上在整理这二者之间的区别时,一般都是这样认为的:

工厂方法模式 克服了 简单工厂模式 违背开放-封闭原则的缺点,是进一步抽象和推广。

但是换个角度而言,工厂方法模式 中,一个具体工厂对应一个具体产品,等于说我写一个产品,就要实现一个工厂,这相对比较笨重,工厂和产品的关系是一对一管理,可是当我的需求变成产品比较固定且产品数量比较小的时候,我会更倾向于用一个工厂去管理所有的产品,而这恰恰就是 简单工厂模式简单工厂模式 可能只是在需求场景变化时的一种进步,在这个场景下,虽然违背了开放-封闭原则,但是好像也无所谓了

而且在谈到违背 开放-封闭原则 的时候,我也有一些其他看法,如果加上客户端的代码一起来看的话,工厂方法模式 实现时,客户端需要决定实例化哪一个工厂来创建具体产品,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断移到了客户端来进行,你想要加新产品,本来是改工厂类的,而现在是修改客户端,不好说,不好说~