工厂方法模式


工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个用于创建对象的接口,但将实际的实例化延迟到子类。这意味着,你不再直接使用 new 关键字来创建对象,而是通过调用一个“工厂方法”来获取对象,这个工厂方法由子类实现,决定了要实例化哪个具体类。

为什么要用工厂方法模式?

在软件开发中,我们经常需要根据不同的条件创建不同类型的对象。如果直接在代码中通过 new 关键字来创建对象,会导致代码与具体的类紧密耦合。当需要添加新的产品类型或者修改现有产品类型时,就需要修改大量的客户端代码,这违反了“开闭原则”(Open-Closed Principle),即对扩展开放,对修改关闭。

工厂方法模式的引入,就是为了解决这个问题。它将对象的创建过程抽象化,使得客户端代码不必关心对象的具体创建细节,从而降低了耦合度,提高了代码的灵活性和可维护性。

核心组成部分

工厂方法模式主要包含以下四个核心角色:

  1. 抽象产品(Abstract Product):定义了工厂方法所创建的对象的接口。所有具体产品都必须实现这个接口。
  2. 具体产品(Concrete Product):实现抽象产品接口的类。工厂方法会创建这些具体产品的实例。
  3. 抽象工厂(Abstract Creator):声明工厂方法,该方法返回一个抽象产品类型的对象。
  4. 具体工厂(Concrete Creator):实现抽象工厂接口,负责实际创建具体产品对象。每个具体工厂都对应一个具体产品。

如何应用工厂方法模式?

现在,我们通过一个实际的例子来一步步学习如何应用工厂方法模式。假设我们要开发一个生产不同类型汽车的游戏。

步骤 1:定义抽象产品

首先,我们需要定义一个抽象的汽车产品,它规定了所有汽车都应该具备的功能。

1
2
3
4
5
6
7
8
# abstract_product.py

class Car:
def drive(self):
pass

def get_info(self):
pass

步骤 2:创建具体产品

接下来,我们创建几种具体的汽车类型,它们都继承自 Car 类,并实现其方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# concrete_product.py

class SedanCar(Car):
def drive(self):
return "Driving a comfortable Sedan car."

def get_info(self):
return "Type: Sedan, Features: Comfortable ride, good for city."

class SUVCar(Car):
def drive(self):
return "Driving a rugged SUV car."

def get_info(self):
return "Type: SUV, Features: Off-road capabilities, spacious."

class SportsCar(Car):
def drive(self):
return "Driving a fast Sports car."

def get_info(self":
return "Type: Sports Car, Features: High speed, sleek design."

步骤 3:定义抽象工厂

然后,我们定义一个抽象的汽车工厂,它包含一个用于创建汽车的工厂方法。

1
2
3
4
5
# abstract_creator.py

class CarFactory:
def create_car(self):
pass

4:创建具体工厂

最后,我们为每种具体的汽车类型创建对应的具体工厂。每个具体工厂都实现 create_car 方法,并返回相应的具体汽车实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# concrete_creator.py

from abstract_product import Car
from concrete_product import SedanCar, SUVCar, SportsCar
from abstract_creator import CarFactory

class SedanCarFactory(CarFactory):
def create_car(self) -> Car:
return SedanCar()

class SUVCarFactory(CarFactory):
def create_car(self) -> Car:
return SUVCar()

class SportsCarFactory(CarFactory):
def create_car(self) -> Car:
return SportsCar()

步骤 5:客户端代码

现在,我们可以使用工厂方法模式来创建汽车了。客户端代码只需要与抽象工厂和抽象产品交互,而无需关心具体的汽车类型是如何创建的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# client.py

from concrete_creator import SedanCarFactory, SUVCarFactory, SportsCarFactory

def client_code(factory):
"""
客户端代码与抽象工厂接口一起工作。
"""
car = factory.create_car()
print(f"Client: I'm using the factory to get a car.")
print(f"Client: {car.get_info()}")
print(f"Client: {car.drive()}")
print("-" * 30)

if __name__ == "__main__":
print("App: Launched with the SedanCarFactory.")
client_code(SedanCarFactory())

print("App: Launched with the SUVCarFactory.")
client_code(SUVCarFactory())

print("App: Launched with the SportsCarFactory.")
client_code(SportsCarFactory())

运行结果

当你运行 client.py 文件时,你会看到如下输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
App: Launched with the SedanCarFactory.
Client: I'm using the factory to get a car.
Client: Type: Sedan, Features: Comfortable ride, good for city.
Client: Driving a comfortable Sedan car.
------------------------------
App: Launched with the SUVCarFactory.
Client: I'm using the factory to get a car.
Client: Type: SUV, Features: Off-road capabilities, spacious.
Client: Driving a rugged SUV car.
------------------------------
App: Launched with the SportsCarFactory.
Client: I'm using the factory to get a car.
Client: Type: Sports Car, Features: High speed, sleek design.
Client: Driving a fast Sports car.
------------------------------

工厂方法模式的优点

  • 解耦:客户端代码与具体的产品类解耦。当需要添加新的产品类型时,只需要添加新的具体产品类和新的具体工厂类,而无需修改客户端代码。
  • 符合开闭原则:对扩展开放,对修改关闭。
  • 单一职责原则:产品创建的职责被移到了具体的工厂类中,每个工厂只负责创建一种产品。
  • 易于维护和扩展:当需要引入新的产品时,只需要增加新的具体产品类和对应的具体工厂类,而无需修改现有代码。

工厂方法模式的缺点

  • 类的数量增加:每增加一个产品,就需要增加一个具体产品类和一个具体工厂类,这会导致类的数量急剧增加,增加系统的复杂度。
  • 代码结构复杂:相对于简单的对象创建,工厂方法模式的代码结构会更复杂一些。

什么时候使用工厂方法模式?

  • 当一个类无法预知它需要创建的对象的类时。
  • 当一个类希望其子类来指定它所创建的对象时。
  • 当类库需要提供一种方法让客户端代码扩展其内部组件时。

试题巩固

  1. 场景决策题: 假设你正在开发一个跨平台的 UI 库,这个库需要能够创建在 Windows、macOS 和 Linux 上看起来和行为都一致的按钮(Button)。但是,不同操作系统创建按钮的底层实现是完全不同的。在这种情况下,工厂方法模式是一个好的选择吗?为什么?

  2. 代码重构题: 请看下面这段代码。它直接在 PizzaStore 类中根据 type 创建不同种类的披萨。如果让你用工厂方法模式来重构它,你会如何修改?请描述你的重构思路,特别是,你会如何把 create_pizza 这个 “工厂” 的职责分离出去?

原始代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Pizza:
def prepare(self):
print(f"Preparing {self.name}")

class CheesePizza(Pizza):
name = "Cheese Pizza"

class VeggiePizza(Pizza):
name = "Veggie Pizza"

class PizzaStore:
def order_pizza(self, type: str) -> Pizza:
pizza = None
if type == 'cheese':
pizza = CheesePizza()
elif type == 'veggie':
pizza = VeggiePizza()

if pizza:
pizza.prepare()
# ... 其他烘焙、切片、打包等流程
return pizza
  1. 模式扩展题: 在使用工厂方法模式时,抽象工厂(Creator)类中除了定义创建产品(Product)的工厂方法(如 create_car())之外,通常还会包含一些与产品相关的通用业务逻辑(如 order_pizza() 中的准备、烘焙、打包流程)。你认为这样设计有什么好处?为什么不把这些业务逻辑直接放在客户端代码里呢?

  2. 辨析题: 工厂方法模式的核心思想是 “将对象的实例化延迟到子类”。请用你自己的话解释一下,这句话中的 “延迟”(defer)体现在哪里?