工厂方法模式¶
分类:创建型模式。来源:《Head First 设计模式》第 4 章。
本页先记录披萨示例从直接创建对象到简单工厂的演进,再补充由具体披萨店实现创建方法的结构。同章的抽象工厂模式进一步负责提供配套原料;完整可运行版本见示例代码。
下面的历史代码片段表示重构的不同阶段,各段涉及的类定义需要分别理解。
类结构¶
classDiagram
direction TB
class Creator {
<<abstract>>
+operation()
+factoryMethod() Product*
}
class ConcreteCreator {
+factoryMethod() Product
}
class Product {
<<interface>>
+use()
}
class ConcreteProduct {
+use()
}
Creator <|-- ConcreteCreator
Product <|.. ConcreteProduct
Creator ..> Product : 使用
ConcreteCreator ..> ConcreteProduct : 创建
角色定义¶
| 角色 | 定义 |
|---|---|
| Creator(创建者) | 声明工厂方法,在 operation() 的业务流程中调用它,并通过 Product 抽象使用返回的对象。 |
| ConcreteCreator(具体创建者) | 重写工厂方法,决定返回哪一种具体产品。 |
| Product(产品接口) | 定义业务流程使用产品时依赖的公共操作。 |
| ConcreteProduct(具体产品) | 实现产品接口,由相应的具体创建者实例化。 |
以制作披萨为例¶
添加更多的Pizza,需要把Pizza定义为抽象类或接口,然后创建Pizza子类。
如果需要添加新的Pizza或者去掉某些不畅销的Pizza,上面的代码需要做以下修改
上面的代码无法让orderPizza()对修改关闭。
创建对象的代码是可变的,对象的方法调用是不可变的。这样我们把变化的抽取出来,我们定义一个简单工厂类来负责Pizza对象的创建。
简单工厂是一种封装对象创建的常见写法,不属于 GoF 列出的 23 种设计模式。这里的 PizzaStore 委托给具体的 SimplePizzaFactory,具体披萨由该工厂内部的条件分支选择。
随着业务的发展,可以选择在不同的地方开分店,每一个店都是一个创建Pizza的工厂,可以通过继承SimplePizzaFactory工厂创建新的工厂。
这些子类目前还是空的,会继承同一套创建逻辑,尚未实现地区差异。仅增加不同地区的类名不会改变实际创建的披萨;后面需要由具体创建者实现不同的创建行为。
定义工厂方法模式¶
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
原书中的披萨店工厂方法¶
PizzaStore 保留订购流程,把创建披萨的步骤声明为可由子类实现的方法。下面省略具体披萨产品类,只展示创建者的结构:
这是对原书结构的教学简化:仅保留两种口味,增加非法类型检查,并将订购流程声明为 final。调用 new NYPizzaStore().orderPizza("cheese") 时,父类流程会调用纽约店实现的 createPizza(),得到纽约风格的奶酪披萨。
工厂方法创建的是 Pizza 对象;orderPizza() 随后使用它完成准备、烘烤、切片和装盒。创建者也可以设计成独立的 Factory 接口及具体实现,类名不必包含 Store。
与抽象工厂的衔接¶
在原书的后续设计中,NYPizzaStore.createPizza() 继续负责创建披萨,但改为给 CheesePizza 注入 NYPizzaIngredientFactory。这样,同一个奶酪披萨类通过不同的原料工厂获得地区配方。
- 工厂方法:由具体创建者决定如何创建披萨。
- 抽象工厂:提供纽约或芝加哥的一整套面团、酱汁、奶酪等原料。
详见抽象工厂笔记。两者可以同时使用;具体创建方法中出现 if 或 switch 不会自动改变模式的性质。
依赖倒置原则¶
设计原则:要依赖抽象,不要依赖具体类。
订购流程依赖 Pizza 的行为约定,原料使用者依赖 PizzaIngredientFactory 及原料接口。具体产品的选择集中在对应的具体创建者中。
参考¶
Head First 示例:门店创建地区风味披萨¶
《Head First 设计模式》第 4 章让纽约店与芝加哥店共用订购流程,同时各自决定制作哪一种地区披萨。本节按作者 2020 年第二版配套代码的 factory/pizzafm 核对:客户同样订购奶酪披萨,纽约店返回 NYStyleCheesePizza,芝加哥店返回 ChicagoStyleCheesePizza;两者的配方由各自构造器设置,芝加哥版本还重写了切片方法。
这里的 Pizza 是抽象类,但 prepare() 等制作方法已有实现;面团和酱汁目前仍是字符串字段。与前文标明的教学简化相比,此版本的 orderPizza() 没有声明为 final,createPizza() 为包级可见,具体门店遇到未识别的类型会返回 null,没有主动抛出参数异常。下面仅追踪源码已支持的奶酪订单。
| 具体类名 | 对应角色 | 职责 |
|---|---|---|
PizzaTestDrive |
客户与示例入口 | 创建两家门店,通过 orderPizza() 下单并读取结果。 |
PizzaStore |
抽象创建者 | 声明 createPizza(),在 orderPizza() 中执行公共制作流程。 |
NYPizzaStore |
具体创建者 | 按口味选择纽约风格的具体披萨。 |
ChicagoPizzaStore |
具体创建者 | 按口味选择芝加哥风格的具体披萨。 |
Pizza |
抽象产品 | 保存名称、配方与配料,提供准备、烘烤、切片和装盒行为。 |
NYStyleCheesePizza |
具体产品 | 设置薄面团、玛丽娜拉酱汁和雷吉亚诺奶酪配方。 |
ChicagoStyleCheesePizza |
具体产品 | 设置厚面团、李子番茄酱汁和马苏里拉奶酪配方,并改为方块切片。 |
示例 UML¶
图中保留两个地区与奶酪这一种口味,省略蛤蜊、香肠、蔬菜披萨,以及配料集合和输出辅助方法。~ 表示包级可见,* 标记抽象方法,$ 标记静态方法;门店对披萨是创建与使用依赖,没有保存披萨的实例字段。
classDiagram
direction TB
class PizzaTestDrive {
+main(String[] args) void$
}
class PizzaStore {
<<abstract>>
~createPizza(String item) Pizza*
+orderPizza(String type) Pizza
}
class NYPizzaStore {
~createPizza(String item) Pizza
}
class ChicagoPizzaStore {
~createPizza(String item) Pizza
}
class Pizza {
<<abstract>>
~String name
~String dough
~String sauce
~prepare() void
~bake() void
~cut() void
~box() void
+getName() String
}
class NYStyleCheesePizza {
+NYStyleCheesePizza()
}
class ChicagoStyleCheesePizza {
+ChicagoStyleCheesePizza()
~cut() void
}
PizzaStore <|-- NYPizzaStore
PizzaStore <|-- ChicagoPizzaStore
Pizza <|-- NYStyleCheesePizza
Pizza <|-- ChicagoStyleCheesePizza
PizzaTestDrive ..> PizzaStore : 下单
PizzaStore ..> Pizza : 创建后调用制作流程
NYPizzaStore ..> NYStyleCheesePizza : 创建
ChicagoPizzaStore ..> ChicagoStyleCheesePizza : 创建
协作过程¶
PizzaTestDrive.main()创建NYPizzaStore和ChicagoPizzaStore,以PizzaStore类型的局部变量引用它们。- 客户向纽约店调用
orderPizza("cheese"),父类流程通过动态分派进入NYPizzaStore.createPizza(),得到已设置纽约配方的NYStyleCheesePizza。 orderPizza()依次调用prepare()、bake()、cut()、box(),随后返回完成制作流程的披萨。- 芝加哥店接收同样的订单时创建
ChicagoStyleCheesePizza;共用流程调用到它重写的cut(),因此切成方块。门店负责选择产品,产品负责自己的行为差异。
固定版本源码: