跳转至

工厂方法模式

分类:创建型模式。来源:《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(具体产品) 实现产品接口,由相应的具体创建者实例化。

以制作披萨为例

1
2
3
4
5
6
7
8
public static Pizza orderPizza(String type){
    Pizza pizza = new Pizza();
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

添加更多的Pizza,需要把Pizza定义为抽象类或接口,然后创建Pizza子类。

public static Pizza orderPizza(String type){
    Pizza pizza = null;
    if(type.equals("cheese")){
        pizza = new CheesePizza();
    }else if(type.equals("greek")){
        pizza = new GreekPizza();
    }else if(type.equals("pepperoni")){
        pizza = new PepperoniPizza();
    }
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

如果需要添加新的Pizza或者去掉某些不畅销的Pizza,上面的代码需要做以下修改

public static Pizza orderPizza(String type){
    Pizza pizza = null;
    if(type.equals("cheese")){
        pizza = new CheesePizza();
    //去掉不畅销的Pizza
    // }else if(type.equals("greek")){
    //     pizza = new GreekPizza();
    }else if(type.equals("pepperoni")){
        pizza = new PepperoniPizza();
    //新添加两种Pizza
    }else if(type.equals("clam")){
        pizza = new ClamPizza();
    }else if(type.equals("veggie")){
        pizza = new VeggiePizza();
    }
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

上面的代码无法让orderPizza()对修改关闭。

创建对象的代码是可变的,对象的方法调用是不可变的。这样我们把变化的抽取出来,我们定义一个简单工厂类来负责Pizza对象的创建。

class SimplePizzaFactory{
    public Pizza createPizza(String type){
        Pizza pizza = null;
        if(type.equals("cheese")){
            pizza = new CheesePizza();
        }else if(type.equals("pepperoni")){
            pizza = new PepperoniPizza();
        }else if(type.equals("clam")){
            pizza = new ClamPizza();
        }else if(type.equals("veggie")){
            pizza = new VeggiePizza();
        }
        return pizza;
    }
}
public class PizzaStore {
    SimplePizzaFactory factory;
    public PizzaStore(SimplePizzaFactory factory){
        this.factory = factory;
    }

    public Pizza orderPizza(String type){
        Pizza pizza = null;
        pizza = factory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

简单工厂是一种封装对象创建的常见写法,不属于 GoF 列出的 23 种设计模式。这里的 PizzaStore 委托给具体的 SimplePizzaFactory,具体披萨由该工厂内部的条件分支选择。

随着业务的发展,可以选择在不同的地方开分店,每一个店都是一个创建Pizza的工厂,可以通过继承SimplePizzaFactory工厂创建新的工厂。

1
2
3
4
5
6
class NYPizzaFactory extends SimplePizzaFactory{
}
class ChicagoPizzaFactory extends SimplePizzaFactory{
}
class CaliforniaPizzaFactory extends SimplePizzaFactory{
}

这些子类目前还是空的,会继承同一套创建逻辑,尚未实现地区差异。仅增加不同地区的类名不会改变实际创建的披萨;后面需要由具体创建者实现不同的创建行为。

1
2
3
4
5
6
NYPizzaFactory nyFactory = new NYPizzaFactory();
PizzaStore nyStore = new PizzaStore(nyFactory);
nyStore.orderPizza("veggie");
ChicagoPizzaFactory chicagoFactory = new ChicagoPizzaFactory();
PizzaStore chicagoStore = new PizzaStore(chicagoFactory);
chicagoStore.orderPizza("veggie");

定义工厂方法模式

工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

原书中的披萨店工厂方法

PizzaStore 保留订购流程,把创建披萨的步骤声明为可由子类实现的方法。下面省略具体披萨产品类,只展示创建者的结构:

abstract class PizzaStore {
    public final Pizza orderPizza(String type) {
        Pizza pizza = createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    protected abstract Pizza createPizza(String type);
}

class NYPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String type) {
        if ("cheese".equals(type)) {
            return new NYStyleCheesePizza();
        }
        if ("clam".equals(type)) {
            return new NYStyleClamPizza();
        }
        throw new IllegalArgumentException("不支持的披萨:" + type);
    }
}

class ChicagoPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String type) {
        if ("cheese".equals(type)) {
            return new ChicagoStyleCheesePizza();
        }
        if ("clam".equals(type)) {
            return new ChicagoStyleClamPizza();
        }
        throw new IllegalArgumentException("不支持的披萨:" + type);
    }
}

这是对原书结构的教学简化:仅保留两种口味,增加非法类型检查,并将订购流程声明为 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 : 创建

协作过程

  1. PizzaTestDrive.main() 创建 NYPizzaStore 和 ChicagoPizzaStore,以 PizzaStore 类型的局部变量引用它们。
  2. 客户向纽约店调用 orderPizza("cheese"),父类流程通过动态分派进入 NYPizzaStore.createPizza(),得到已设置纽约配方的 NYStyleCheesePizza。
  3. orderPizza() 依次调用 prepare()、bake()、cut()、box(),随后返回完成制作流程的披萨。
  4. 芝加哥店接收同样的订单时创建 ChicagoStyleCheesePizza;共用流程调用到它重写的 cut(),因此切成方块。门店负责选择产品,产品负责自己的行为差异。

固定版本源码:

评论