工厂方法 Factory Method

工厂方法模式是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

工厂模式分为:简单工厂、工厂方法、抽象工厂。


为什么要使用?

工厂方法模式的对象职责:

在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。换句话说,父类只需要知道它能创建对象,具体什么是实例对象让子类去考虑。

例如在实际开发中,你需要用户能够去自定义组件,那么你就需要制定一套规则,让用户依照你的规则去开发。工厂方法模式🏭就是非常好的选择。它能够让创建产品和使用产品分离,那么用户就可以在不修改核心代码的同时,更改为自定义组件。

父类决定实例的生成方式,但并不决定所要生成的具体的类,具体的类全部交由子类去负责。这样就能让创建者和具体产品解耦。


模式结构

  • 产品 (Product) 将会对接口进行声明。 对于所有由创建者及其子类构建的对象, 这些接口都是通用的。
  • 具体产品 (Concrete Products) 是产品接口的不同实现。
  • 创建者 (Creator) 类声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。注意, 并不一定每次调用工厂方法都会创建新的实例。 工厂方法也可以返回缓存、 对象池或其他来源的已有对象。
  • 具体创建者 (Concrete Creators) 将会重写基础工厂方法, 使其返回不同类型的产品。

工厂方法模式的类图:

Creator 类只需要知道 create() 方法可以创建出 Product,而不需要知道其他具体实现。

当我们更换实现为新的 ConcreteCreator 类时,那么 create() 方法创建的 ConcreteProduct 也会更改。如果 Creator 类中能够创建不同的 Product 时,它就离抽象工厂更近一步了。

工厂方法模式侧重于直接对具体产品的实现进行封装和调用,通过统一的接口定义来约束程序的对外行为。


模式实现

该示例使用工厂方法模式来构建 DialogButton 的关系,并提供了一套默认样式。该模式使得用户也可以自定义样式,只需要继承抽象类并实现。

示例程序的类图

代码实现

抽象创建者 Creator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package example.framework;

/** 抽象对话框 */
public abstract class Dialog {
/** 显示对话框 */
public void show() {
Button button = createButton();
button.render();
}

/**
* 创建按键组件
* @return 按键组件实例
*/
public abstract Button createButton();
}
抽象产品 Product
1
2
3
4
5
6
7
8
9
10
11
12
package example.framework;

/** 抽象按键产品 */
public abstract class Button {
/** 按键标签 */
protected String label = "Button";

/**
* 展示按键
*/
public abstract void render();
}
默认实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package example.concrete;

import example.framework.Button;
import example.framework.Dialog;

/** 默认对话框实现 */
public class DefaultDialog extends Dialog {
/**
* 创建按键组件
* @return 按键组件实例
*/
@Override
public Button createButton() {
return new DefaultButton();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
package example.concrete;

import example.framework.Button;

/** 默认按键产品实现 */
public class DefaultButton extends Button {
/** 展示按键 */
@Override
public void render() {
System.out.println("| " + this.label + " |");
}
}
自定义实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package example.my;

import example.framework.Button;
import example.framework.Dialog;

/** 自定义对话框实现 */
public class MyDialog extends Dialog {
/**
* 创建按键组件
* @return 按键组件实例
*/
@Override
public Button createButton() {
return new MyButton();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
package example.my;

import example.framework.Button;

/** 自定义按键产品实现 */
public class MyButton extends Button {
/** 展示按键 */
@Override
public void render() {
System.out.println("( " + this.label + " )");
}
}

代码测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import example.concrete.DefaultDialog;
import example.framework.Dialog;
import example.my.MyDialog;

/** 测试工厂方法模式 */
public class Test {
public static void main(String[] args) {
Dialog defaultDialog = new DefaultDialog();
defaultDialog.show();
System.out.println("------------- 分割线 -------------");
Dialog myDialog = new MyDialog();
myDialog.show();
}
}

输出结果

1
2
3
| Button |
------------- 分割线 -------------
( Button )

由上述可以看出,我们不需要去修改默认实现类就可以将样式替换成自定义的。


常用场景和解决方案

  • 有限、可重用的对象,使用工厂方法模式可以有效节约资源,例如:
    • 需要使用很多重复代码创建对象时,比如,DAO 层的数据对象、API 层的 VO 对象等。
    • 创建对象要访问外部信息或资源时,比如,读取数据库字段、获取访问授权 token 信息、配置文件等。
    • 创建需要统一管理生命周期的对象时,比如,会话信息、用户网页浏览轨迹对象等。
    • 创建池化对象时,比如,连接池对象、线程池对象、日志对象等。
  • 希望隐藏对象的真实类型时,比如,不希望使用者知道对象的真实构造函数参数等。
  • 不确定对象确切类别及其依赖关系时,可以使用工厂方法。工厂方法能让你在使用时不考虑它以后可能会拓展的具体实现。
  • 如果你希望用户能扩展你软件库或框架的内部组件,可使用工厂方法。

模式的优缺点

优点 缺点
你可以避免创建者和具体产品之间的紧密耦合。 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 最好的情况是将该模式引入创建者类的现有层次结构中。
能根据用户的需求定制化地创建对象。 具体工厂实现逻辑不统一,增加代码理解难度。
隐藏了具体使用哪种产品来创建对象。
单一职责原则。 你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
开闭原则。 无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。

拓展知识

  • 在许多设计工作的初期都会使用工厂方法模式(较为简单, 而且可以更方便地通过子类进行定制),随后演化为使用抽象工厂模式、原型模式或生成器模式(更灵活但更加复杂)。
  • 抽象工厂模式通常基于一组工厂方法,但你也可以使用原型模式来生成这些类的方法。
  • 工厂方法和抽象工厂的区别:
    1. 工厂方法模式侧重于继承的连续性,核心为“里氏替换原则”;而抽象工厂模式侧重于组合的拓展性,核心为“分析共性,找出更好的抽象产品”。
    2. 工厂方法适用单产品,抽象工厂适用于多产品。


🔙 设计模式

📌最后:希望本文能够给您提供帮助,文章中有不懂或不正确的地方,请在下方评论区💬留言!

🔗参考文献:

🌐 设计模式 –refactoringguru

▶️ bilibili-趣学设计模式;黄靖锋. –拉勾教育

📖 图解设计模式 /(日)结城浩著;杨文轩译. –北京:人民邮电出版社,2017.1