更多最新文章欢迎大家访问我的个人博客:smile::豆腐别馆
  
上文介绍的简单工厂模式提到了当所需生产的产品逐渐增多时,其违反了单一职责原则及开闭原则,而工厂方法模式,即是对简单工厂模式的进一步抽象化。

一、模式的定义

  • 定义创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。

二、模式的实现

接着上文的栗子,我们已经知道了男人都爱吃豆腐,还爱吃大猪蹄子。在简单工厂方法模式中,我们是通过一个食物工厂类,再依据传入的参数来确认需要生产哪种食物。同时我们也指出,当产品变多后这样处理违反了我们的单一职责原则以及开闭原则。那我们要怎么办呢?

在解决问题前我们需要再明确遍问题出现的原因,不就是因为人类变心喜欢吃上了越来越多的产品,导致工厂严重堵车嘛。知道原因了那可不就好办了,打死变心的,呸, 一个工厂处理不过来我给你两个(N)个工厂帮忙分担行不行?emm,感觉好有道理,说干就干:

首先同样的,能吃到哪样东西的前提是要有这么个东西,同时为了方便用户自定义具体食品,我们也需要新建一个食物父类,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.doufuplus.patterns.factory.method;

/**
* 食物类
* 转载请注明出处,更多技术文章欢迎大家访问我的个人博客站点:https://www.doufuplus.com
*
* @author 丶doufu
* @date 2019/11/27
*/
public abstract class Food {

public abstract void eat();
}

具体食物继承自食物类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.doufuplus.patterns.factory.method;

/**
* 豆腐类
* 转载请注明出处,更多技术文章欢迎大家访问我的个人博客站点:https://www.doufuplus.com
*
* @author 丶doufu
* @date 2019/11/27
*/
public class Doufu extends Food {

@Override
public void eat() {
System.out.println("软软的豆腐...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.doufuplus.patterns.factory.method;

/**
* 猪蹄类
* 转载请注明出处,更多技术文章欢迎大家访问我的个人博客站点:https://www.doufuplus.com
*
* @author 丶doufu
* @date 2019/11/27
*/
public class Trotter extends Food {

@Override
public void eat() {
System.out.println("大大的猪蹄...");
}
}

- Round 1 简单工厂拆分

  1. 新建豆腐工厂类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.doufuplus.patterns.factory.method;

/**
* 豆腐工厂类
* 转载请注明出处,更多技术文章欢迎大家访问我的个人博客站点:https://www.doufuplus.com
*
* @author 丶doufu
* @date 2019/11/27
*/
public class DoufuFactory {

/**
* 获取豆腐
*/
public static Food getDoufu() {
return new Doufu();
}

}
  1. 新建猪蹄工具类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.doufuplus.patterns.factory.method;


/**
* 猪蹄工厂类
* 转载请注明出处,更多技术文章欢迎大家访问我的个人博客站点:https://www.doufuplus.com
*
* @author 丶doufu
* @date 2019/11/27
*/
public class TrotterFactory {

/**
* 获取猪蹄
*/
public static Food getTrotter() {
return new Trotter();
}

}
  1. 现在工厂拆分也拆分了,想吃豆腐了就是这样的:
    Round 1运行结果
    好嘛,这下清晰多了,想吃什么就从什么工厂里面拿,如果这时候我不想吃豆腐了,只想吃大猪蹄子补补要怎么办?那好办,改改代码呗,把上面的DoufuFactory.getDoufu();改为TrotterFactory.getTrotter()就好了。这样当然是可以的,只是这样的改动就变成不止需要替换掉工厂类,还需要去找到更改后相应的方法,那么有什么更好的方式可以使代码改动更少呢?

- Round 2 抽象/多态工厂拆分

身为一个有志向的青年,本着能躺着就不站着的原则。我们需要针对上面提出的问题对代码做出如下改进:

  1. 再次新建食物工厂接口,提供抽象方法,让具体子类去决定生成何种食物:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package com.doufuplus.patterns.factory.method;


    /**
    * 食物工厂类
    * 转载请注明出处,更多技术文章欢迎大家访问我的个人博客站点:https://www.doufuplus.com
    *
    * @author 丶doufu
    * @date 2019/11/27
    */
    public interface FoodFactory {

    /**
    * 获取食物
    */
    Food getFood();
    }
  2. 将原来的DoufuFactory以及FoodFactory都实现上面的FoodFactory,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.doufuplus.patterns.factory.method;

/**
* 豆腐工厂类
* 转载请注明出处,更多技术文章欢迎大家访问我的个人博客站点:https://www.doufuplus.com
*
* @author 丶doufu
* @date 2019/11/27
*/
public class DoufuFactory implements FoodFactory {

/**
* 获取豆腐
*/
@Override
public Food getFood() {
// ...
// 放葱花撒盐
// ...
return new Doufu();
}
}
  1. 那么这时候我们的调用方式就是这样的:
    Round 2运行结果01
  2. 当豆腐吃腻了,只想吃猪蹄时,就是这样的:Round 2运行结果02
    可以看到这时候的代码改动就变少了,且不需要费心去找相应的方法,因为方法名始终是一致的。

ps:当需要生成的产品不多且不会增加,一个具体工厂类就可以完成任务时,可删除抽象工厂类。这时工厂方法模式将退化到上文的简单工厂模式。

三、模式的结构

回顾下上面的代码我们来捋一下工厂方法模式的主要类结构,如下:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。如代码中的FoodFactory
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。如代码中的DoufuFactoryTrotterFactory
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。如代码中的Food类。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。如代码中的DoufuTrotter
    工厂方法类结构图

    四、模式的优缺点

    1. 优点
  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。(与简单工厂模式类似)
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则。

2. 缺点

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,当工厂类越来越多时,易造成工厂泛滥,增加系统复杂度。

五、模式的应用场景

  • 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
  • 创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
  • 客户不关心创建产品的细节,只关心产品的品牌。

参考:C语言中文网-设计模式