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导出两种格式,每种格式下又有图表、表格、页眉页脚这些组件。抽象工厂让新增一种导出格式变得非常简单——只需要新增一个具体工厂,所有组件一次性搞定。

抽象工厂的结构图

下面这张图展示了抽象工厂模式的核心结构。我习惯用这种图来跟团队沟通设计思路,一目了然。

抽象工厂 UIFactory Windows工厂 WindowsFactory Mac工厂 MacFactory 抽象产品A Button Windows按钮 WindowsButton Mac按钮 MacButton 抽象产品B TextBox Windows文本框 WindowsTextBox Mac文本框 MacTextBox 客户端代码 抽象类/接口 具体实现 继承/实现 创建关系

什么时候用抽象工厂?

我个人总结了几条经验,供你参考:

  • 系统需要独立于产品的创建、组合和表示——客户端只关心接口,不关心具体实现
  • 系统要配置多个产品族中的某一个——比如切换数据库、切换主题、切换平台
  • 产品族中的产品需要一起使用,且约束一致——Windows按钮必须配Windows文本框,不能混搭
  • 你想提供一个产品类库,只暴露接口不暴露实现——抽象工厂天然支持这种封装

避坑指南:我曾经在一个项目中过度使用抽象工厂,为每个小产品族都建了一套工厂体系。结果项目后期新增产品时,要同时修改抽象工厂接口和所有具体工厂,改动量很大。抽象工厂适合产品族相对稳定的场景,如果产品族经常变化,反而会增加维护成本。

抽象工厂 vs 工厂方法

很多初学者会混淆这两个模式。我简单对比一下:

对比维度 工厂方法 抽象工厂
创建对象数量 一个工厂创建一个产品 一个工厂创建一组产品
产品关系 产品之间无关联 产品属于同一产品族
扩展方式 新增一个工厂类 新增一个工厂类(同时实现多个创建方法)
使用场景 产品等级结构单一 多个产品等级结构需要协同

说白了,工厂方法解决的是"一个产品怎么创建"的问题,抽象工厂解决的是"一组相关产品怎么一起创建"的问题。你想想看,如果你的系统里只有按钮这一种UI组件,用工厂方法就够了。但如果还有文本框、菜单栏、滚动条,而且它们必须风格统一,那就得上抽象工厂。

我的习惯:在项目初期,我通常先用工厂方法。当发现多个产品开始出现"绑定关系"时,再重构为抽象工厂。不要一开始就上抽象工厂,过度设计比没有设计更可怕。

抽象工厂的优缺点

优点:

  • 隔离了具体类的创建,客户端只依赖抽象接口
  • 保证产品族内产品的一致性
  • 切换产品族非常方便,只需换一个具体工厂
  • 符合开闭原则——新增产品族时,无需修改现有代码

缺点:

  • 扩展产品等级结构很困难——需要修改抽象工厂接口,所有具体工厂都得跟着改
  • 增加了系统的抽象层次,代码量会多一些
  • 如果产品族数量不多,反而显得臃肿

抽象工厂模式,说白了就是"把一群有血缘关系的工厂打包管理"。我在实际项目中用得不算特别频繁,但一旦遇到跨平台、多主题、多数据库这类场景,它就是最优雅的解决方案。记住一点:模式是工具,不是目的。用对了地方,它就是利器;用错了地方,它就是累赘。


公众号:蓝海资料掘金营,微信deep3321