11、抽象工厂模式:Python实现、产品族与产品等级结构的概念
抽象工厂模式,说白了就是工厂模式的升级版。我刚开始接触设计模式时,觉得工厂模式已经够用了,直到我在一个跨平台UI项目中碰了壁——不同操作系统下的按钮、文本框、菜单栏,它们之间不是简单的"换一个类"就能解决的。嗯,这时候抽象工厂就派上用场了。
产品族与产品等级结构
要理解抽象工厂,先得搞明白两个概念:产品族和产品等级结构。这两个词听起来很学术,其实不难。
产品等级结构,就是同一类产品的继承体系。比如按钮这个抽象产品,有Windows按钮、Mac按钮、Linux按钮这些具体产品。它们都继承自同一个抽象按钮,这就是一个产品等级结构。
产品族,指的是由同一个工厂生产的一组相关产品。比如Windows平台下,有Windows按钮、Windows文本框、Windows菜单栏,它们组合在一起就是一个产品族。
你想想看,如果每个产品等级结构都用一个独立工厂,那客户端代码就得同时管理多个工厂,耦合度很高。抽象工厂就是把这些相关的工厂整合到一起,形成一个"超级工厂"。
核心思想:抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
Python实现示例
我拿一个跨平台UI组件库来举例。假设我们要支持Windows和Mac两种风格,每个风格下都有按钮和文本框。
先定义抽象产品:
from abc import ABC, abstractmethod
class Button(ABC):
@abstractmethod
def paint(self):
pass
class TextBox(ABC):
@abstractmethod
def display(self):
pass
然后实现具体产品:
class WindowsButton(Button):
def paint(self):
return "绘制Windows风格按钮"
class MacButton(Button):
def paint(self):
return "绘制Mac风格按钮"
class WindowsTextBox(TextBox):
def display(self):
return "显示Windows风格文本框"
class MacTextBox(TextBox):
def display(self):
return "显示Mac风格文本框"
接下来是抽象工厂和具体工厂:
class UIFactory(ABC):
@abstractmethod
def create_button(self) -> Button:
pass
@abstractmethod
def create_textbox(self) -> TextBox:
pass
class WindowsFactory(UIFactory):
def create_button(self) -> Button:
return WindowsButton()
def create_textbox(self) -> TextBox:
return WindowsTextBox()
class MacFactory(UIFactory):
def create_button(self) -> Button:
return MacButton()
def create_textbox(self) -> TextBox:
return MacTextBox()
客户端使用起来就非常干净:
def create_ui(factory: UIFactory):
button = factory.create_button()
textbox = factory.create_textbox()
print(button.paint())
print(textbox.display())
# 切换平台只需换一个工厂
create_ui(WindowsFactory())
create_ui(MacFactory())
个人经验:我在一个金融报表系统中用过这个模式。当时需要支持PDF导出和Excel导出两种格式,每种格式下又有图表、表格、页眉页脚这些组件。抽象工厂让新增一种导出格式变得非常简单——只需要新增一个具体工厂,所有组件一次性搞定。
抽象工厂的结构图
下面这张图展示了抽象工厂模式的核心结构。我习惯用这种图来跟团队沟通设计思路,一目了然。
什么时候用抽象工厂?
我个人总结了几条经验,供你参考:
- 系统需要独立于产品的创建、组合和表示——客户端只关心接口,不关心具体实现
- 系统要配置多个产品族中的某一个——比如切换数据库、切换主题、切换平台
- 产品族中的产品需要一起使用,且约束一致——Windows按钮必须配Windows文本框,不能混搭
- 你想提供一个产品类库,只暴露接口不暴露实现——抽象工厂天然支持这种封装
避坑指南:我曾经在一个项目中过度使用抽象工厂,为每个小产品族都建了一套工厂体系。结果项目后期新增产品时,要同时修改抽象工厂接口和所有具体工厂,改动量很大。抽象工厂适合产品族相对稳定的场景,如果产品族经常变化,反而会增加维护成本。
抽象工厂 vs 工厂方法
很多初学者会混淆这两个模式。我简单对比一下:
| 对比维度 | 工厂方法 | 抽象工厂 |
|---|---|---|
| 创建对象数量 | 一个工厂创建一个产品 | 一个工厂创建一组产品 |
| 产品关系 | 产品之间无关联 | 产品属于同一产品族 |
| 扩展方式 | 新增一个工厂类 | 新增一个工厂类(同时实现多个创建方法) |
| 使用场景 | 产品等级结构单一 | 多个产品等级结构需要协同 |
说白了,工厂方法解决的是"一个产品怎么创建"的问题,抽象工厂解决的是"一组相关产品怎么一起创建"的问题。你想想看,如果你的系统里只有按钮这一种UI组件,用工厂方法就够了。但如果还有文本框、菜单栏、滚动条,而且它们必须风格统一,那就得上抽象工厂。
我的习惯:在项目初期,我通常先用工厂方法。当发现多个产品开始出现"绑定关系"时,再重构为抽象工厂。不要一开始就上抽象工厂,过度设计比没有设计更可怕。
抽象工厂的优缺点
优点:
- 隔离了具体类的创建,客户端只依赖抽象接口
- 保证产品族内产品的一致性
- 切换产品族非常方便,只需换一个具体工厂
- 符合开闭原则——新增产品族时,无需修改现有代码
缺点:
- 扩展产品等级结构很困难——需要修改抽象工厂接口,所有具体工厂都得跟着改
- 增加了系统的抽象层次,代码量会多一些
- 如果产品族数量不多,反而显得臃肿
抽象工厂模式,说白了就是"把一群有血缘关系的工厂打包管理"。我在实际项目中用得不算特别频繁,但一旦遇到跨平台、多主题、多数据库这类场景,它就是最优雅的解决方案。记住一点:模式是工具,不是目的。用对了地方,它就是利器;用错了地方,它就是累赘。
公众号:蓝海资料掘金营,微信deep3321