设计模式之工厂方法模式

设计模式之工厂方法模式

意图

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

  • 工厂模式中,增加一种产品类,就要增加一个工厂类:因为每个工厂类只能创建一种产品的实例。
  • 工厂模式遵循“开放-封闭原则”:工厂模式中,新增一种产品并不需要修改原有类,仅仅是扩展。

简单工厂模式相比于工厂方法模式

优点:工厂类中包含必要的逻辑判断,可根据客户端的选择条件动态实例化需要的类。对于客户端来说,去除了对具体产品的依赖。

缺点违背了开放封闭原则。 每添加一个新的产品,都需要对原有类进行修改。增加维护成本,且不易于维护。

开放封闭原则:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

适用场景

  • 当你在编写代码的过程中, 如果无法预知对象确切类别及其依赖关系时, 可使用工厂方法。
  • 如果你希望用户能扩展你软件库或框架的内部组件, 可使用工厂方法。
  • 如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象, 可使用工厂方法。

结构

img

结构说明

  1. 产品 (Product) 将会对接口进行声明。 对于所有由创建者及其子类构建的对象, 这些接口都是通用的。
  2. 具体产品 (Concrete Products) 是产品接口的不同实现。
  3. 创建者 (Creator) 类声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。
  • 你可以将工厂方法声明为抽象方法, 强制要求每个子类以不同方式实现该方法。 或者, 你也可以在基础工厂方法中返回默认产品类型。
  • 注意, 尽管它的名字是创建者, 但他最主要的职责并不是创建产品。 一般来说, 创建者类包含一些与产品相关的核心业务逻辑。 工厂方法将这些逻辑处理从具体产品类中分离出来。 打个比方, 大型软件开发公司拥有程序员培训部门。 但是, 这些公司的主要工作还是编写代码, 而非生产程序员。
  1. 具体创建者 (Concrete Creators) 将会重写基础工厂方法, 使其返回不同类型的产品。
    注意, 并不一定每次调用工厂方法都会创建新的实例。 工厂方法也可以返回缓存、 对象池或其他来源的已有对象。

结构代码范式

【Product】

定义产品对象的接口。

1
2
3
abstract class Product {
public abstract void use();
}

【ConcreteProduct】

实现 Product 接口。

1
2
3
4
5
6
7
8
9
10
class ConcreteProduct extends Product {
public ConcreteProduct() {
System.out.println("创建 ConcreteProduct 产品");
}

@Override
public void Use() {
System.out.println("使用 ConcreteProduct 产品");
}
}

【Creator】

声明工厂方法,它会返回一个产品类型的对象Creator 也可以实现一个默认的工厂方法 factoryMethod() ,以返回一个默认的具体产品类型。

1
2
3
interface Creator {
public Product factoryMethod();
}

【ConcreteCreator】

覆写 Creator 中的工厂方法 factoryMethod()

1
2
3
4
5
6
class ConcreteCreator implements Creator {
@Override
public Product factoryMethod() {
return new ConcreteProduct();
}
}

【客户端】

1
2
3
4
5
6
7
public class factoryMethodPattern {
public static void main(String[] args) {
Creator factory = new ConcreteCreator();
Product product = factory.factoryMethod();
product.Use();
}
}

【输出】

1
2
创建 ConcreteProduct 产品
使用 ConcreteProduct 产品

伪代码

以下示例演示了如何使用工厂方法开发跨平台 UI (用户界面) 组件, 并同时避免客户代码与具体 UI 类之间的耦合。

img

基础对话框类使用不同的 UI 组件渲染窗口。 在不同的操作系统下, 这些组件外观或许略有不同, 但其功能保持一致。 Windows 系统中的按钮在 Linux 系统中仍然是按钮。

如果使用工厂方法, 就不需要为每种操作系统重写对话框逻辑。 如果我们声明了一个在基本对话框类中生成按钮的工厂方法, 那么我们就可以创建一个对话框子类, 并使其通过工厂方法返回 Windows 样式按钮。 子类将继承对话框基础类的大部分代码, 同时在屏幕上根据 Windows 样式渲染按钮。

如需该模式正常工作, 基础对话框类必须使用抽象按钮 (例如基类或接口), 以便将其扩展为具体按钮。 这样一来, 无论对话框中使用何种类型的按钮, 其代码都可以正常工作。

你可以使用此方法开发其他 UI 组件。 不过, 每向对话框中添加一个新的工厂方法, 你就离抽象工厂模式更近一步。 我们将在稍后谈到这个模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 创建者类声明的工厂方法必须返回一个产品类的对象。创建者的子类通常会提供
// 该方法的实现。
class Dialog is
// 创建者还可提供一些工厂方法的默认实现。
abstract method createButton():Button

// 请注意,创建者的主要职责并非是创建产品。其中通常会包含一些核心业务
// 逻辑,这些逻辑依赖于由工厂方法返回的产品对象。子类可通过重写工厂方
// 法并使其返回不同类型的产品来间接修改业务逻辑。
method render() is
// 调用工厂方法创建一个产品对象。
Button okButton = createButton()
// 现在使用产品。
okButton.onClick(closeDialog)
okButton.render()


// 具体创建者将重写工厂方法以改变其所返回的产品类型。
class WindowsDialog extends Dialog is
method createButton():Button is
return new WindowsButton()

class WebDialog extends Dialog is
method createButton():Button is
return new HTMLButton()


// 产品接口中将声明所有具体产品都必须实现的操作。
interface Button is
method render()
method onClick(f)

// 具体产品需提供产品接口的各种实现。
class WindowsButton implements Button is
method render(a, b) is
// 根据 Windows 样式渲染按钮。
method onClick(f) is
// 绑定本地操作系统点击事件。

class HTMLButton implements Button is
method render(a, b) is
// 返回一个按钮的 HTML 表述。
method onClick(f) is
// 绑定网络浏览器的点击事件。


class Application is
field dialog: Dialog

// 程序根据当前配置或环境设定选择创建者的类型。
method initialize() is
config = readApplicationConfigFile()

if (config.OS == "Windows") then
dialog = new WindowsDialog()
else if (config.OS == "Web") then
dialog = new WebDialog()
else
throw new Exception("错误!未知的操作系统。")

// 当前客户端代码会与具体创建者的实例进行交互,但是必须通过其基本接口
// 进行。只要客户端通过基本接口与创建者进行交互,你就可将任何创建者子
// 类传递给客户端。
method main() is
this.initialize()
dialog.render()

案例

使用示例: 工厂方法模式在 Java 代码中得到了广泛使用。 当你需要在代码中提供高层次的灵活性时, 该模式会非常实用。

核心 Java 程序库中有该模式的应用:

识别方法: 工厂方法可通过构建方法来识别, 它会创建具体类的对象, 但以抽象类型或接口的形式返回这些对象。

还是以 简单工厂模式 里的例子来进行说明。

如何实现一个具有加减乘除基本功能的计算器?

两种模式的 ProductConcreteProduct 角色代码没有区别,不再赘述。

差异在于 Factory 角色部分,以及客户端部分,请在代码中体会。

【Creator 角色】

1
2
3
4
// Creator 角色,定义返回产品实例的公共工厂方法
interface OperationFactory {
public Operation factoryMethod();
}

【ConcreteCreator 角色】

和简单工厂模式相比,每一种产品都会有一个具体的工厂类负责生产实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ConcreteCreator 角色,具体实现 Creator 中的方法
class AddFactory implements OperationFactory {
@Override
public Operation factoryMethod() {
return new Add();
}
}

// ConcreteCreator 角色,具体实现 Creator 中的方法
class SubFactory implements OperationFactory {
@Override
public Operation factoryMethod() {
return new Sub();
}
}

// ConcreteCreator 角色,具体实现 Creator 中的方法
class MulFactory implements OperationFactory {
@Override
public Operation factoryMethod() {
return new Mul();
}
}

// ConcreteCreator 角色,具体实现 Creator 中的方法
class DivFactory implements OperationFactory {
@Override
public Operation factoryMethod() {
return new Div();
}
}

【Client 角色】

与简单工厂模式中无需关注具体创建不同,工厂模式中需要指定具体工厂,以负责生产具体对应的产品。

1
2
3
4
5
6
7
8
9
10
11
// Client 角色,需要指定具体工厂,以负责生产具体产品
public class factoryMethodPattern {
public static void main(String[] args) {
OperationFactory factory = new SubFactory();
Operation oper = factory.factoryMethod();
oper.numA = 3;
oper.numB = 2;
double result = oper.getResult();
System.out.println("result = " + result);
}
}

与其他模式的关系

参考资料