03、Python - 抽象工厂模式:从造车到跨平台架构,一篇讲透产品族的创建之道

本文为原创文章,如需转载,请联系作者获得授权,并注明出处。
文章目录
- [03、Python - 抽象工厂模式:从造车到跨平台架构,一篇讲透产品族的创建之道](#03、Python - 抽象工厂模式:从造车到跨平台架构,一篇讲透产品族的创建之道)
-
- 一、痛点场景:你是否也曾被"串味"的代码折磨?
-
- [1.1 场景一:跨平台UI的"风格混乱"](#1.1 场景一:跨平台UI的"风格混乱")
- [1.2 场景二:数据库切换的"牵一发而动全身"](#1.2 场景二:数据库切换的"牵一发而动全身")
- [1.3 场景三:造车时的"零部件来源失控"](#1.3 场景三:造车时的"零部件来源失控")
- 二、什么是抽象工厂模式?
-
- [2.1 专业解释](#2.1 专业解释)
- [2.2 大白话](#2.2 大白话)
- [2.3 生活案例](#2.3 生活案例)
- 三、为什么要用抽象工厂模式?
-
- [3.1 保证产品族的一致性](#3.1 保证产品族的一致性)
- [3.2 切换产品族只需改一处](#3.2 切换产品族只需改一处)
- [3.3 客户端与具体实现解耦](#3.3 客户端与具体实现解耦)
- [3.4 有利于产品的一致性约束](#3.4 有利于产品的一致性约束)
- 四、演进之路:从简单工厂到抽象工厂
-
- [4.1 第一阶段:直接new对象(原始时代)](#4.1 第一阶段:直接new对象(原始时代))
- [4.2 第二阶段:简单工厂模式(青铜时代)](#4.2 第二阶段:简单工厂模式(青铜时代))
- [4.3 第三阶段:工厂方法模式(白银时代)](#4.3 第三阶段:工厂方法模式(白银时代))
- [4.4 第四阶段:抽象工厂模式(黄金时代)](#4.4 第四阶段:抽象工厂模式(黄金时代))
- [4.5 三种工厂模式对比](#4.5 三种工厂模式对比)
- 五、核心概念:产品族与产品等级
-
- [5.1 产品等级结构](#5.1 产品等级结构)
- [5.2 产品族](#5.2 产品族)
- [5.3 一张图看懂](#5.3 一张图看懂)
- [5.4 关键洞察](#5.4 关键洞察)
- 六、四大角色与UML类图
-
- [6.1 四大角色](#6.1 四大角色)
- [6.2 客户端如何使用](#6.2 客户端如何使用)
- 七、怎么用:Python标准实现
-
- [7.1 使用abc模块定义抽象基类](#7.1 使用abc模块定义抽象基类)
- [7.2 完整实现:跨平台UI组件](#7.2 完整实现:跨平台UI组件)
- [7.3 代码解读](#7.3 代码解读)
- 八、常用场景教学
-
- [8.1 场景一:跨平台UI工具包](#8.1 场景一:跨平台UI工具包)
- [8.2 场景二:多主题/换肤系统](#8.2 场景二:多主题/换肤系统)
- [8.3 场景三:数据库访问层](#8.3 场景三:数据库访问层)
- [8.4 场景四:云服务SDK抽象](#8.4 场景四:云服务SDK抽象)
- [8.5 场景五:游戏开发中的种族/阵营系统](#8.5 场景五:游戏开发中的种族/阵营系统)
- [8.6 场景六:支付网关系统](#8.6 场景六:支付网关系统)
- 九、企业项目实战:多云存储抽象工厂
-
- [9.1 业务背景](#9.1 业务背景)
- [9.2 不使用抽象工厂的痛点](#9.2 不使用抽象工厂的痛点)
- [9.3 抽象工厂实现](#9.3 抽象工厂实现)
- [9.4 企业级设计要点](#9.4 企业级设计要点)
- [9.5 如何扩展新的云服务商](#9.5 如何扩展新的云服务商)
- 十、优缺点分析
-
- [10.1 优点](#10.1 优点)
- [10.2 缺点](#10.2 缺点)
- [10.3 适用场景总结](#10.3 适用场景总结)
- 十一、面试官高频面试题
- 十二、总结
-
- [12.1 核心思想回顾](#12.1 核心思想回顾)
- [12.2 关键概念速记](#12.2 关键概念速记)
- [12.3 使用决策流程](#12.3 使用决策流程)
- [12.4 一句话记住抽象工厂](#12.4 一句话记住抽象工厂)
一、痛点场景:你是否也曾被"串味"的代码折磨?
1.1 场景一:跨平台UI的"风格混乱"
假设你正在开发一套跨平台的桌面应用,需要同时支持Windows和macOS。界面上有按钮、文本框、复选框三种组件。
新手最容易写出这样的代码:
python
class WindowsButton:
def render(self):
print("渲染Windows风格按钮")
class MacButton:
def render(self):
print("渲染Mac风格按钮")
class WindowsTextField:
def render(self):
print("渲染Windows风格文本框")
class MacTextField:
def render(self):
print("渲染Mac风格文本框")
# 业务代码中到处都是if-else
def build_ui(platform):
if platform == "windows":
button = WindowsButton()
text_field = WindowsTextField()
elif platform == "mac":
button = MacButton()
text_field = MacTextField()
button.render()
text_field.render()
看起来没问题?等一下,当团队有5个开发者、20个页面、10种组件时,灾难就来了:
- 开发者A在某个页面写了
WindowsButton(),却配了MacTextField(),界面风格直接"串味" - 新增Linux平台时,需要在20个页面里逐个修改if-else,漏改一个就是线上bug
- 组件从10种增加到15种时,每个平台的工厂都要改一遍
这就是典型的产品族一致性问题:一组相关的产品必须配套使用,但代码无法强制保证这一点。
1.2 场景二:数据库切换的"牵一发而动全身"
再看一个后端常见场景。你的系统需要支持MySQL和PostgreSQL两种数据库,每种数据库都有连接对象、命令对象、事务对象。
python
# 糟糕的实现:业务代码直接依赖具体类
def save_user(user_data):
conn = MySQLConnection("localhost", 3306, "mydb")
cmd = MySQLCommand(conn)
tx = MySQLTransaction(conn)
tx.begin()
try:
cmd.execute("INSERT INTO users ...")
tx.commit()
except Exception:
tx.rollback()
当客户要求切换到PostgreSQL时,你发现整个项目里有上百处这样的代码,每一处都要改。更可怕的是,有人可能混用了MySQLConnection和PostgreSQLCommand,运行时直接崩溃。
1.3 场景三:造车时的"零部件来源失控"
回到文章开头提到的造车案例。在普通工厂模式下,造一辆奔驰车时,如果不对零部件来源加以控制,可能出现:
- 轮胎用了宝马的
- 底盘用了奥迪的
- 其他零件用了不知名小厂的
最终这辆"奔驰"开起来异响不断,4S店还推卸责任说"零件不是我们的"。
抽象工厂模式要解决的,正是这一类问题:当一组相关产品必须一起使用、且需要整体切换时,如何保证一致性并降低切换成本?
二、什么是抽象工厂模式?
2.1 专业解释
抽象工厂模式(Abstract Factory Pattern)是一种创建型设计模式,它提供一个接口,用于创建一系列相关或相互依赖的对象(即一个"产品族"),而无需指定它们具体的类。
该模式围绕一个"超级工厂"创建其他工厂,这个超级工厂又被称为"工厂的工厂"。
核心要点:
- 一个抽象工厂接口声明了多个创建方法,每个方法对应一种产品
- 每个具体工厂实现该接口,负责创建一个完整产品族的所有产品
- 客户端只依赖抽象工厂和抽象产品接口,不关心具体实现
2.2 大白话
说人话就是:抽象工厂就是一个"品牌总代理",它不直接生产产品,而是管着一堆代工厂。你告诉总代理"我要苹果全家桶",它就给你拿出iPhone、iPad、MacWatch、AirPods,全都是苹果品牌的,保证配套。你说"我要华为全家桶",它立刻换成华为的手机、平板、手表、耳机,风格统一,不会出现"苹果手机配华为充电器"这种尴尬场面。
普通工厂模式是"一个工厂造一种产品",抽象工厂模式是"一个工厂造一整套配套产品"。
2.3 生活案例
案例一:家具套装
你去宜家买卧室家具,有"北欧风格"和"中式风格"两个系列。每个系列都包含床、衣柜、床头柜、台灯。
- 北欧工厂:生产北欧床、北欧衣柜、北欧床头柜、北欧台灯
- 中式工厂:生产中式床、中式衣柜、中式床头柜、中式台灯
你选了北欧风格,导购员(抽象工厂)就给你配齐一整套北欧家具,不会出现"北欧床配中式衣柜"的混搭灾难。
案例二:餐饮套餐
麦当劳有"汉堡套餐"和"鸡肉卷套餐"。每个套餐都包含主食、小吃、饮料。
- 汉堡套餐工厂:汉堡 + 薯条 + 可乐
- 鸡肉卷套餐工厂:鸡肉卷 + 鸡块 + 橙汁
你点了汉堡套餐,收银员(抽象工厂)确保给你的是汉堡配薯条配可乐,而不是汉堡配鸡块配橙汁。
案例三:古代兵工厂
中国古代打仗,朝廷有"军械监"(抽象工厂),下设"弓弩坊"和"甲胄坊"等具体工坊。
- 步兵装备工厂:步弓 + 皮甲 + 环首刀
- 骑兵装备工厂:骑弓 + 铁甲 + 马槊
将军下令"装备骑兵",军械监就统一发放骑兵的一整套装备,保证弓、甲、兵器都是为骑兵设计的,不会给骑兵发一把步兵用的长戈(骑马根本挥不开)。
三、为什么要用抽象工厂模式?
3.1 保证产品族的一致性
这是抽象工厂最核心的价值。当一组产品被设计成一起使用时,抽象工厂从架构层面强制保证了"同族产品一起使用",避免了混搭导致的运行时错误或风格混乱。
比如数据库场景中,MySQLConnection只能搭配MySQLCommand,抽象工厂确保你不会拿到一个PostgreSQLCommand去操作MySQLConnection。
3.2 切换产品族只需改一处
当需要从一个产品族切换到另一个时(比如从Windows风格切换到Mac风格,从MySQL切换到PostgreSQL),只需要替换具体工厂实例,业务代码完全不用改。
python
# 切换前
factory = WindowsFactory()
# 切换后,只改这一行
factory = MacFactory()
# 下面的业务代码完全不变
button = factory.create_button()
text_field = factory.create_text_field()
3.3 客户端与具体实现解耦
客户端只依赖抽象工厂接口和抽象产品接口,完全不知道具体产品类的存在。这符合依赖倒置原则:高层模块不依赖低层模块,二者都依赖抽象。
3.4 有利于产品的一致性约束
当产品族内部有约束关系时(比如某种按钮只能搭配某种文本框),抽象工厂把这些约束封装在工厂内部,客户端无需了解这些复杂规则。
四、演进之路:从简单工厂到抽象工厂

抽象工厂不是凭空出现的,它是工厂模式不断演进的结果。让我们沿着时间线,看看它是怎么一步步进化来的。
4.1 第一阶段:直接new对象(原始时代)
最早的时候,程序员直接在业务代码里new对象:
python
def order_pizza():
pizza = CheesePizza() # 直接依赖具体类
pizza.bake()
return pizza
问题:要换一种披萨,就得改业务代码,违反开闭原则。
4.2 第二阶段:简单工厂模式(青铜时代)
为了解决直接new的问题,人们把创建逻辑抽到一个工厂类里:
python
class SimplePizzaFactory:
def create_pizza(self, pizza_type):
if pizza_type == "cheese":
return CheesePizza()
elif pizza_type == "pepperoni":
return PepperoniPizza()
def order_pizza(factory, pizza_type):
pizza = factory.create_pizza(pizza_type)
pizza.bake()
return pizza
优点:客户端不再直接new对象。
缺点:新增披萨类型时需要修改工厂类的if-else,违反开闭原则;一个工厂只能创建一种产品。
4.3 第三阶段:工厂方法模式(白银时代)
为了解决简单工厂违反开闭原则的问题,人们把工厂也抽象化,每个产品对应一个工厂:
python
from abc import ABC, abstractmethod
class PizzaFactory(ABC):
@abstractmethod
def create_pizza(self):
pass
class CheesePizzaFactory(PizzaFactory):
def create_pizza(self):
return CheesePizza()
class PepperoniPizzaFactory(PizzaFactory):
def create_pizza(self):
return PepperoniPizza()
优点:新增产品只需新增工厂,符合开闭原则。
缺点:一个工厂只能生产一种产品。当需要生产一组相关产品时,就需要管理大量工厂,而且无法保证产品之间的配套关系。
4.4 第四阶段:抽象工厂模式(黄金时代)
当业务发展到需要"一整套配套产品"时,抽象工厂模式应运而生:
python
from abc import ABC, abstractmethod
class GUIFactory(ABC):
@abstractmethod
def create_button(self):
pass
@abstractmethod
def create_text_field(self):
pass
class WindowsFactory(GUIFactory):
def create_button(self):
return WindowsButton()
def create_text_field(self):
return WindowsTextField()
class MacFactory(GUIFactory):
def create_button(self):
return MacButton()
def create_text_field(self):
return MacTextField()
一个工厂管一整套产品,切换工厂就是切换整套风格。
4.5 三种工厂模式对比
| 对比维度 | 简单工厂 | 工厂方法 | 抽象工厂 |
|---|---|---|---|
| 核心思想 | 一个工厂根据参数创建不同产品 | 让子类决定实例化哪个产品 | 创建一系列相关产品族 |
| 产品数量 | 多种产品共用一个工厂 | 每个产品对应一个工厂 | 一个工厂生产一族产品 |
| 工厂接口方法数 | 1个(带参数) | 1个(无参数) | 多个(每种产品一个) |
| 开闭原则 | 违反(新增产品改工厂) | 符合(新增产品加工厂) | 倾斜性符合(新增产品族容易,新增产品等级困难) |
| 适用场景 | 产品少且稳定 | 单一产品类型,变体多 | 多产品配套使用,需整体切换 |
| 复杂度 | 低 | 中 | 高 |
五、核心概念:产品族与产品等级

理解抽象工厂模式,必须先搞清楚两个核心概念:产品族 和产品等级结构。
5.1 产品等级结构
产品等级结构是指同一类产品的继承结构,即抽象产品和它的具体实现构成的层次。
比如:
- 按钮(抽象产品)← Windows按钮 / Mac按钮 / Linux按钮(具体产品)
- 文本框(抽象产品)← Windows文本框 / Mac文本框 / Linux文本框
- 空调(抽象产品)← 美的空调 / 格力空调 / 海尔空调
产品等级结构是纵向的,描述的是"同一种东西的不同品牌/实现"。
5.2 产品族
产品族是指由同一个具体工厂生产的、位于不同产品等级结构中的一组产品。
比如:
- Windows产品族:Windows按钮 + Windows文本框 + Windows复选框
- Mac产品族:Mac按钮 + Mac文本框 + Mac复选框
- 美的产品族:美的空调 + 美的冰箱 + 美的洗衣机
产品族是横向的,描述的是"同一个品牌下的不同种类产品"。
5.3 一张图看懂
产品等级1(按钮) 产品等级2(文本框) 产品等级3(复选框)
│ │ │
├─ Windows按钮 ├─ Windows文本框 ├─ Windows复选框 ← Windows产品族
├─ Mac按钮 ├─ Mac文本框 ├─ Mac复选框 ← Mac产品族
└─ Linux按钮 └─ Linux文本框 └─ Linux复选框 ← Linux产品族
- 纵向看:每一列是一个产品等级结构
- 横向看:每一行是一个产品族
- 抽象工厂:每一行对应一个具体工厂
5.4 关键洞察
抽象工厂模式的本质是:用工厂来隔离产品族,确保同一族的产品被一起使用。
- 新增一个产品族(比如新增Linux平台):只需新增一个具体工厂,符合开闭原则
- 新增一个产品等级(比如新增"滚动条"组件):需要修改所有具体工厂,违反开闭原则
这就是抽象工厂模式的"倾斜性开闭原则":对产品族的扩展开放,对产品等级的扩展关闭。
六、四大角色与UML类图

6.1 四大角色
抽象工厂模式包含四个核心角色:
| 角色 | 职责 | 对应代码 |
|---|---|---|
| 抽象工厂(AbstractFactory) | 声明创建抽象产品对象的接口,包含多个创建方法 | GUIFactory |
| 具体工厂(ConcreteFactory) | 实现抽象工厂接口,生成一个产品族的所有具体产品 | WindowsFactory、MacFactory |
| 抽象产品(AbstractProduct) | 为每种产品声明接口 | Button、TextField |
| 具体产品(ConcreteProduct) | 定义具体工厂生产的具体产品对象,实现抽象产品接口 | WindowsButton、MacButton |
6.2 客户端如何使用
客户端(Client)只使用抽象工厂和抽象产品声明的接口,完全不接触具体类:
python
def client_code(factory: GUIFactory):
button = factory.create_button()
text_field = factory.create_text_field()
button.render()
text_field.render()
客户端拿到的是抽象工厂,调用它的创建方法得到抽象产品,然后调用抽象产品的方法。整个过程中,客户端不知道也不关心具体是Windows还是Mac。
七、怎么用:Python标准实现

7.1 使用abc模块定义抽象基类
Python中实现抽象工厂模式,标准做法是使用abc模块(Abstract Base Classes)。abc.ABC是抽象基类,@abstractmethod装饰器声明抽象方法。
7.2 完整实现:跨平台UI组件
下面以跨平台UI组件为例,给出完整的Python实现:
python
from abc import ABC, abstractmethod
# ========== 抽象产品 ==========
class Button(ABC):
"""抽象产品:按钮"""
@abstractmethod
def render(self) -> None:
pass
@abstractmethod
def click(self) -> None:
pass
class TextField(ABC):
"""抽象产品:文本框"""
@abstractmethod
def render(self) -> None:
pass
@abstractmethod
def set_text(self, text: str) -> None:
pass
class Checkbox(ABC):
"""抽象产品:复选框"""
@abstractmethod
def render(self) -> None:
pass
@abstractmethod
def toggle(self) -> None:
pass
# ========== 具体产品:Windows风格 ==========
class WindowsButton(Button):
def render(self):
print("[Windows] 渲染方形按钮")
def click(self):
print("[Windows] 按钮被点击,发出'咔'的声音")
class WindowsTextField(TextField):
def render(self):
print("[Windows] 渲染直角文本框")
def set_text(self, text: str):
print(f"[Windows] 文本框内容设置为: {text}")
class WindowsCheckbox(Checkbox):
def __init__(self):
self.checked = False
def render(self):
status = "勾选" if self.checked else "未勾选"
print(f"[Windows] 渲染方形复选框 [{status}]")
def toggle(self):
self.checked = not self.checked
print(f"[Windows] 复选框切换为: {'勾选' if self.checked else '未勾选'}")
# ========== 具体产品:Mac风格 ==========
class MacButton(Button):
def render(self):
print("[Mac] 渲染圆角按钮")
def click(self):
print("[Mac] 按钮被点击,触感反馈")
class MacTextField(TextField):
def render(self):
print("[Mac] 渲染圆角文本框")
def set_text(self, text: str):
print(f"[Mac] 文本框内容设置为: {text}")
class MacCheckbox(Checkbox):
def __init__(self):
self.checked = False
def render(self):
status = "勾选" if self.checked else "未勾选"
print(f"[Mac] 渲染圆形复选框 [{status}]")
def toggle(self):
self.checked = not self.checked
print(f"[Mac] 复选框切换为: {'勾选' if self.checked else '未勾选'}")
# ========== 抽象工厂 ==========
class GUIFactory(ABC):
"""抽象工厂:GUI组件工厂"""
@abstractmethod
def create_button(self) -> Button:
pass
@abstractmethod
def create_text_field(self) -> TextField:
pass
@abstractmethod
def create_checkbox(self) -> Checkbox:
pass
# ========== 具体工厂 ==========
class WindowsFactory(GUIFactory):
"""Windows风格组件工厂"""
def create_button(self) -> Button:
return WindowsButton()
def create_text_field(self) -> TextField:
return WindowsTextField()
def create_checkbox(self) -> Checkbox:
return WindowsCheckbox()
class MacFactory(GUIFactory):
"""Mac风格组件工厂"""
def create_button(self) -> Button:
return MacButton()
def create_text_field(self) -> TextField:
return MacTextField()
def create_checkbox(self) -> Checkbox:
return MacCheckbox()
# ========== 客户端代码 ==========
def build_login_page(factory: GUIFactory):
"""使用抽象工厂构建登录页面,完全不依赖具体平台"""
print("=== 开始构建登录页面 ===")
username_field = factory.create_text_field()
password_field = factory.create_text_field()
remember_checkbox = factory.create_checkbox()
login_button = factory.create_button()
username_field.render()
username_field.set_text("user@example.com")
password_field.render()
password_field.set_text("********")
remember_checkbox.render()
remember_checkbox.toggle()
login_button.render()
login_button.click()
print("=== 登录页面构建完成 ===\n")
if __name__ == "__main__":
print("---------- 使用Windows风格 ----------")
build_login_page(WindowsFactory())
print("---------- 使用Mac风格 ----------")
build_login_page(MacFactory())
运行结果:
---------- 使用Windows风格 ----------
=== 开始构建登录页面 ===
[Windows] 渲染直角文本框
[Windows] 文本框内容设置为: user@example.com
[Windows] 渲染直角文本框
[Windows] 文本框内容设置为: ********
[Windows] 渲染方形复选框 [未勾选]
[Windows] 复选框切换为: 勾选
[Windows] 渲染方形按钮
[Windows] 按钮被点击,发出'咔'的声音
=== 登录页面构建完成 ===
---------- 使用Mac风格 ----------
=== 开始构建登录页面 ===
[Mac] 渲染圆角文本框
[Mac] 文本框内容设置为: user@example.com
[Mac] 渲染圆角文本框
[Mac] 文本框内容设置为: ********
[Mac] 渲染圆形复选框 [未勾选]
[Mac] 复选框切换为: 勾选
[Mac] 渲染圆角按钮
[Mac] 按钮被点击,触感反馈
=== 登录页面构建完成 ===
7.3 代码解读
- 抽象产品层 :
Button、TextField、Checkbox三个抽象基类,定义了每种组件的接口 - 具体产品层:每个抽象产品有Windows和Mac两个具体实现
- 抽象工厂层 :
GUIFactory声明了三个创建方法,分别对应三种产品 - 具体工厂层 :
WindowsFactory和MacFactory分别生产各自风格的一整套组件 - 客户端 :
build_login_page只接收GUIFactory抽象类型,完全不知道具体平台
切换平台时,只需把 WindowsFactory() 换成 MacFactory(),业务代码一行都不用改。
八、常用场景教学
8.1 场景一:跨平台UI工具包
这是抽象工厂模式最经典的应用场景。Java的Swing、Python的Tkinter、Qt框架等都在底层使用了类似的思想。
适用条件:
- 同一套业务逻辑需要运行在多个操作系统上
- 每个平台的UI组件外观和行为不同
- 同一页面的组件必须保持同一种平台风格
实现要点:
- 抽象工厂定义
create_button()、create_text_field()、create_menu()等方法 - 每个平台一个具体工厂
- 客户端通过配置或环境变量选择具体工厂
8.2 场景二:多主题/换肤系统
很多应用支持"亮色主题"和"暗色主题",甚至更多自定义主题。每个主题下,按钮颜色、背景色、文字颜色、边框样式都是配套的。
python
class ThemeFactory(ABC):
@abstractmethod
def create_button_style(self):
pass
@abstractmethod
def create_background_style(self):
pass
@abstractmethod
def create_text_style(self):
pass
class LightThemeFactory(ThemeFactory):
def create_button_style(self):
return {"bg": "#007AFF", "text": "#FFFFFF"}
def create_background_style(self):
return {"bg": "#FFFFFF"}
def create_text_style(self):
return {"color": "#000000"}
class DarkThemeFactory(ThemeFactory):
def create_button_style(self):
return {"bg": "#0A84FF", "text": "#FFFFFF"}
def create_background_style(self):
return {"bg": "#1C1C1E"}
def create_text_style(self):
return {"color": "#FFFFFF"}
切换主题就是切换工厂,保证所有元素的颜色风格一致,不会出现"亮色背景配暗色文字"的可读性灾难。
8.3 场景三:数据库访问层
当系统需要支持多种数据库(MySQL、PostgreSQL、Oracle、SQLite)时,每种数据库都有自己的连接对象、命令对象、事务对象、数据读取器。
为什么用抽象工厂而不是工厂方法?
因为连接、命令、事务之间有依赖关系:MySQL的命令对象只能操作MySQL的连接,PostgreSQL的命令只能操作PostgreSQL的连接。抽象工厂确保你拿到的是配套的一整套数据库对象。
python
class DatabaseFactory(ABC):
@abstractmethod
def create_connection(self, host, port, db_name):
pass
@abstractmethod
def create_command(self, connection):
pass
@abstractmethod
def create_transaction(self, connection):
pass
8.4 场景四:云服务SDK抽象
企业应用经常需要对接多家云服务商(AWS、Azure、阿里云、腾讯云),每家都有对象存储、云服务器、数据库、CDN等服务。
抽象工厂可以把"云服务商"作为产品族,把"对象存储、云服务器、数据库"作为产品等级:
python
class CloudFactory(ABC):
@abstractmethod
def create_object_storage(self):
pass
@abstractmethod
def create_vm(self):
pass
@abstractmethod
def create_database(self):
pass
class AliyunFactory(CloudFactory):
def create_object_storage(self):
return OSSStorage()
def create_vm(self):
return ECSServer()
def create_database(self):
return RDSDatabase()
class AWSFactory(CloudFactory):
def create_object_storage(self):
return S3Storage()
def create_vm(self):
return EC2Server()
def create_database(self):
return RDSEngine()
切换云服务商只需替换工厂,业务代码完全不变。
8.5 场景五:游戏开发中的种族/阵营系统
在策略游戏或RPG游戏中,不同种族(人类、兽人、精灵)有不同的兵种、建筑、科技。每个种族的兵种和建筑是配套的:人类农场只能给人类提供食物,兽人地洞只能给兽人提供人口。
python
class RaceFactory(ABC):
@abstractmethod
def create_warrior(self):
pass
@abstractmethod
def create_mage(self):
pass
@abstractmethod
def create_building(self):
pass
class HumanFactory(RaceFactory):
def create_warrior(self):
return Footman()
def create_mage(self):
return Sorceress()
def create_building(self):
return TownHall()
class OrcFactory(RaceFactory):
def create_warrior(self):
return Grunt()
def create_mage(self):
return Shaman()
def create_building(self):
return GreatHall()
8.6 场景六:支付网关系统
电商平台需要支持多种支付方式(支付宝、微信支付、银联、PayPal),每种支付方式都有支付接口、退款接口、对账接口、查询接口。这些接口必须来自同一个支付渠道,不能用支付宝的支付接口配微信的退款接口。
python
class PaymentFactory(ABC):
@abstractmethod
def create_pay_service(self):
pass
@abstractmethod
def create_refund_service(self):
pass
@abstractmethod
def create_reconcile_service(self):
pass
class AlipayFactory(PaymentFactory):
def create_pay_service(self):
return AlipayPayService()
def create_refund_service(self):
return AlipayRefundService()
def create_reconcile_service(self):
return AlipayReconcileService()
class WeChatPayFactory(PaymentFactory):
def create_pay_service(self):
return WeChatPayService()
def create_refund_service(self):
return WeChatRefundService()
def create_reconcile_service(self):
return WeChatReconcileService()
九、企业项目实战:多云存储抽象工厂

上面的例子偏教学,下面给一个真正能在企业项目中使用的完整案例:多云存储抽象工厂。
9.1 业务背景
某创业公司的产品需要支持文件存储,最初只用阿里云OSS。后来为了降低成本和避免厂商锁定,需要同时支持腾讯云COS和AWS S3。
每种云存储都有三个核心操作:
- 上传文件
- 下载文件
- 删除文件
- 生成临时访问URL
而且,每种云存储的配置(密钥、区域、桶名)和客户端初始化方式都不同。
9.2 不使用抽象工厂的痛点
python
# 糟糕的实现:业务代码里到处判断云厂商
def upload_file(cloud_type, file_path, key):
if cloud_type == "aliyun":
client = AliyunOSSClient(access_key, secret_key, endpoint)
client.put_object(bucket_name, key, file_path)
elif cloud_type == "tencent":
client = TencentCOSClient(secret_id, secret_key, region)
client.upload_file(bucket_name, key, file_path)
elif cloud_type == "aws":
client = AWSS3Client(aws_access_key_id, aws_secret_access_key, region_name)
client.upload_file(bucket_name, key, file_path)
问题:
- 每个操作(上传、下载、删除、生成URL)都要写一遍if-else
- 新增云厂商时,所有操作都要修改
- 配置管理混乱,不同云的密钥散落在各处
- 无法统一做日志、监控、重试
9.3 抽象工厂实现
python
from abc import ABC, abstractmethod
from typing import Optional, Dict, Any
import datetime
# ========== 抽象产品:存储客户端 ==========
class StorageClient(ABC):
"""抽象产品:统一的存储客户端接口"""
@abstractmethod
def upload(self, key: str, file_path: str) -> str:
"""上传文件,返回文件的唯一标识"""
pass
@abstractmethod
def download(self, key: str, save_path: str) -> bool:
"""下载文件到本地路径"""
pass
@abstractmethod
def delete(self, key: str) -> bool:
"""删除文件"""
pass
@abstractmethod
def generate_presigned_url(self, key: str, expires_in: int = 3600) -> str:
"""生成临时访问URL"""
pass
# ========== 具体产品:阿里云OSS ==========
class AliyunOSSStorage(StorageClient):
def __init__(self, access_key_id: str, access_key_secret: str,
endpoint: str, bucket_name: str):
self.access_key_id = access_key_id
self.access_key_secret = access_key_secret
self.endpoint = endpoint
self.bucket_name = bucket_name
# 实际项目中这里初始化 oss2.Bucket
print(f"[AliyunOSS] 初始化客户端, bucket={bucket_name}, endpoint={endpoint}")
def upload(self, key: str, file_path: str) -> str:
print(f"[AliyunOSS] 上传文件: {file_path} -> {key}")
# 实际调用: self.bucket.put_object(key, file_path)
return f"oss://{self.bucket_name}/{key}"
def download(self, key: str, save_path: str) -> bool:
print(f"[AliyunOSS] 下载文件: {key} -> {save_path}")
# 实际调用: self.bucket.get_object_to_file(key, save_path)
return True
def delete(self, key: str) -> bool:
print(f"[AliyunOSS] 删除文件: {key}")
# 实际调用: self.bucket.delete_object(key)
return True
def generate_presigned_url(self, key: str, expires_in: int = 3600) -> str:
print(f"[AliyunOSS] 生成临时URL: {key}, 有效期{expires_in}秒")
# 实际调用: self.bucket.sign_url('GET', key, expires_in)
return f"https://{self.bucket_name}.{self.endpoint}/{key}?Expires=xxx"
# ========== 具体产品:腾讯云COS ==========
class TencentCOSStorage(StorageClient):
def __init__(self, secret_id: str, secret_key: str,
region: str, bucket_name: str):
self.secret_id = secret_id
self.secret_key = secret_key
self.region = region
self.bucket_name = bucket_name
# 实际项目中这里初始化 cos_config + CosS3Client
print(f"[TencentCOS] 初始化客户端, bucket={bucket_name}, region={region}")
def upload(self, key: str, file_path: str) -> str:
print(f"[TencentCOS] 上传文件: {file_path} -> {key}")
# 实际调用: self.client.upload_file(Bucket=self.bucket_name, Key=key, LocalFilePath=file_path)
return f"cos://{self.bucket_name}/{key}"
def download(self, key: str, save_path: str) -> bool:
print(f"[TencentCOS] 下载文件: {key} -> {save_path}")
# 实际调用: self.client.download_file(Bucket=self.bucket_name, Key=key, DestFilePath=save_path)
return True
def delete(self, key: str) -> bool:
print(f"[TencentCOS] 删除文件: {key}")
# 实际调用: self.client.delete_object(Bucket=self.bucket_name, Key=key)
return True
def generate_presigned_url(self, key: str, expires_in: int = 3600) -> str:
print(f"[TencentCOS] 生成临时URL: {key}, 有效期{expires_in}秒")
# 实际调用: self.client.get_presigned_url(Method='GET', Bucket=self.bucket_name, Key=key, Expired=expires_in)
return f"https://{self.bucket_name}.cos.{self.region}.myqcloud.com/{key}?sign=xxx"
# ========== 具体产品:AWS S3 ==========
class AWSS3Storage(StorageClient):
def __init__(self, aws_access_key_id: str, aws_secret_access_key: str,
region_name: str, bucket_name: str):
self.aws_access_key_id = aws_access_key_id
self.aws_secret_access_key = aws_secret_access_key
self.region_name = region_name
self.bucket_name = bucket_name
# 实际项目中这里初始化 boto3.client('s3')
print(f"[AWSS3] 初始化客户端, bucket={bucket_name}, region={region_name}")
def upload(self, key: str, file_path: str) -> str:
print(f"[AWSS3] 上传文件: {file_path} -> {key}")
# 实际调用: self.s3.upload_file(file_path, self.bucket_name, key)
return f"s3://{self.bucket_name}/{key}"
def download(self, key: str, save_path: str) -> bool:
print(f"[AWSS3] 下载文件: {key} -> {save_path}")
# 实际调用: self.s3.download_file(self.bucket_name, key, save_path)
return True
def delete(self, key: str) -> bool:
print(f"[AWSS3] 删除文件: {key}")
# 实际调用: self.s3.delete_object(Bucket=self.bucket_name, Key=key)
return True
def generate_presigned_url(self, key: str, expires_in: int = 3600) -> str:
print(f"[AWSS3] 生成临时URL: {key}, 有效期{expires_in}秒")
# 实际调用: self.s3.generate_presigned_url('get_object', Params={'Bucket': self.bucket_name, 'Key': key}, ExpiresIn=expires_in)
return f"https://{self.bucket_name}.s3.{self.region_name}.amazonaws.com/{key}?X-Amz-Signature=xxx"
# ========== 抽象工厂 ==========
class StorageFactory(ABC):
"""抽象工厂:存储服务工厂"""
@abstractmethod
def create_storage(self) -> StorageClient:
"""创建存储客户端"""
pass
@abstractmethod
def get_provider_name(self) -> str:
"""获取云服务商名称"""
pass
# ========== 具体工厂 ==========
class AliyunOSSFactory(StorageFactory):
def __init__(self, config: Dict[str, Any]):
self.config = config
def create_storage(self) -> StorageClient:
return AliyunOSSStorage(
access_key_id=self.config["access_key_id"],
access_key_secret=self.config["access_key_secret"],
endpoint=self.config["endpoint"],
bucket_name=self.config["bucket_name"]
)
def get_provider_name(self) -> str:
return "aliyun_oss"
class TencentCOSFactory(StorageFactory):
def __init__(self, config: Dict[str, Any]):
self.config = config
def create_storage(self) -> StorageClient:
return TencentCOSStorage(
secret_id=self.config["secret_id"],
secret_key=self.config["secret_key"],
region=self.config["region"],
bucket_name=self.config["bucket_name"]
)
def get_provider_name(self) -> str:
return "tencent_cos"
class AWSS3Factory(StorageFactory):
def __init__(self, config: Dict[str, Any]):
self.config = config
def create_storage(self) -> StorageClient:
return AWSS3Storage(
aws_access_key_id=self.config["aws_access_key_id"],
aws_secret_access_key=self.config["aws_secret_access_key"],
region_name=self.config["region_name"],
bucket_name=self.config["bucket_name"]
)
def get_provider_name(self) -> str:
return "aws_s3"
# ========== 工厂注册中心(企业级增强) ==========
class StorageFactoryRegistry:
"""工厂注册中心:通过配置动态选择工厂,支持运行时扩展"""
_factories: Dict[str, type] = {}
@classmethod
def register(cls, provider_name: str, factory_class: type):
"""注册新的云服务商工厂"""
cls._factories[provider_name] = factory_class
print(f"[Registry] 注册云服务商: {provider_name}")
@classmethod
def create_factory(cls, provider_name: str, config: Dict[str, Any]) -> StorageFactory:
"""根据配置创建具体工厂"""
factory_class = cls._factories.get(provider_name)
if not factory_class:
raise ValueError(f"不支持的云服务商: {provider_name}, 已注册: {list(cls._factories.keys())}")
return factory_class(config)
# 注册内置工厂
StorageFactoryRegistry.register("aliyun_oss", AliyunOSSFactory)
StorageFactoryRegistry.register("tencent_cos", TencentCOSFactory)
StorageFactoryRegistry.register("aws_s3", AWSS3Factory)
# ========== 业务层:文件服务 ==========
class FileService:
"""业务层文件服务,完全依赖抽象接口"""
def __init__(self, storage: StorageClient):
self.storage = storage
def upload_avatar(self, user_id: int, file_path: str) -> str:
key = f"avatars/{user_id}/{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.jpg"
result = self.storage.upload(key, file_path)
url = self.storage.generate_presigned_url(key, expires_in=86400)
print(f"[FileService] 用户{user_id}头像上传成功, 访问URL: {url}")
return url
def delete_avatar(self, user_id: int, key: str):
self.storage.delete(key)
print(f"[FileService] 用户{user_id}头像已删除")
# ========== 客户端使用 ==========
def main():
# 模拟从配置文件读取
config = {
"provider": "aliyun_oss", # 改成 "tencent_cos" 或 "aws_s3" 即可切换
"aliyun_oss": {
"access_key_id": "your_access_key",
"access_key_secret": "your_secret",
"endpoint": "oss-cn-guangzhou.aliyuncs.com",
"bucket_name": "my-app-bucket"
},
"tencent_cos": {
"secret_id": "your_secret_id",
"secret_key": "your_secret_key",
"region": "ap-guangzhou",
"bucket_name": "my-app-bucket-1250000000"
},
"aws_s3": {
"aws_access_key_id": "your_aws_key",
"aws_secret_access_key": "your_aws_secret",
"region_name": "ap-southeast-1",
"bucket_name": "my-app-bucket"
}
}
# 根据配置创建工厂
provider = config["provider"]
factory = StorageFactoryRegistry.create_factory(provider, config[provider])
print(f"当前使用云服务商: {factory.get_provider_name()}\n")
# 创建存储客户端
storage = factory.create_storage()
# 业务层使用
file_service = FileService(storage)
file_service.upload_avatar(user_id=1001, file_path="/tmp/avatar.jpg")
print()
# 切换到腾讯云(只改配置,不改业务代码)
print("=" * 50)
print("切换到腾讯云COS:")
print("=" * 50)
factory2 = StorageFactoryRegistry.create_factory("tencent_cos", config["tencent_cos"])
storage2 = factory2.create_storage()
file_service2 = FileService(storage2)
file_service2.upload_avatar(user_id=1002, file_path="/tmp/avatar2.jpg")
if __name__ == "__main__":
main()
9.4 企业级设计要点
- 工厂注册中心 :通过
StorageFactoryRegistry实现工厂的动态注册和发现,新增云服务商只需调用register(),无需修改已有代码 - 配置驱动:通过配置文件指定使用哪个云服务商,切换云厂商无需重新部署
- 业务层隔离 :
FileService只依赖StorageClient抽象接口,完全不知道底层是阿里云还是AWS - 统一接口:四种操作(上传、下载、删除、生成URL)统一接口,业务代码无需关心各云厂商的API差异
- 可测试性 :可以轻松Mock一个
StorageClient用于单元测试
9.5 如何扩展新的云服务商
假设现在要新增华为云OBS,只需三步:
python
# 第一步:实现具体产品
class HuaweiOBSStorage(StorageClient):
def __init__(self, ak, sk, server, bucket_name):
# 初始化 obs.ObsClient
pass
def upload(self, key, file_path):
pass # 实现华为云上传逻辑
def download(self, key, save_path):
pass
def delete(self, key):
pass
def generate_presigned_url(self, key, expires_in=3600):
pass
# 第二步:实现具体工厂
class HuaweiOBSFactory(StorageFactory):
def __init__(self, config):
self.config = config
def create_storage(self):
return HuaweiOBSStorage(
ak=self.config["ak"],
sk=self.config["sk"],
server=self.config["server"],
bucket_name=self.config["bucket_name"]
)
def get_provider_name(self):
return "huawei_obs"
# 第三步:注册到工厂中心
StorageFactoryRegistry.register("huawei_obs", HuaweiOBSFactory)
完成!已有业务代码一行都不用改,配置里把provider改成huawei_obs即可使用。
十、优缺点分析
10.1 优点
1. 保证产品族的一致性
抽象工厂从架构层面确保同一产品族的对象一起使用,避免了混搭导致的运行时错误。这是它最核心、不可替代的价值。
2. 切换产品族极其方便
从一个产品族切换到另一个,只需替换具体工厂实例,业务代码完全不变。这对于需要支持多平台、多主题、多数据库的系统来说,维护成本大幅降低。
3. 客户端与具体实现解耦
客户端只依赖抽象接口,符合依赖倒置原则。具体产品类的变化不会影响客户端代码。
4. 有利于产品的一致性约束
产品族内部的约束关系(如某种数据库的命令只能搭配某种连接)被封装在工厂内部,客户端无需了解这些复杂规则。
5. 符合开闭原则(产品族维度)
新增一个产品族时,只需新增一个具体工厂,无需修改已有代码,符合开闭原则。
10.2 缺点
1. 新增产品等级困难(倾斜性开闭原则)
这是抽象工厂模式最大的痛点。如果需要新增一种产品(比如UI组件从按钮、文本框、复选框增加到滚动条),就需要:
- 修改抽象工厂接口,新增
create_scrollbar()方法 - 修改所有具体工厂,实现这个新方法
- 可能影响所有已有的客户端代码
这违反了开闭原则。因此,抽象工厂模式适用于产品等级结构稳定的场景。
2. 类的数量增多,复杂度上升
每个产品族需要一个具体工厂,每个产品需要一个抽象产品和多个具体产品。当产品族和产品等级都很多时,类的数量会急剧增加,系统复杂度上升。
3. 抽象层增加,理解成本提高
对于新手来说,抽象工厂模式的多层抽象(抽象工厂、具体工厂、抽象产品、具体产品)不如直接new对象直观,学习和理解成本较高。
4. 过度设计风险
如果产品族只有一个,或者产品之间没有配套使用的约束,使用抽象工厂模式就是过度设计,反而增加了不必要的复杂度。
10.3 适用场景总结
适合使用抽象工厂模式的场景:
- 系统需要独立于产品的创建、组合和表示
- 系统中有多个产品族,且每次只使用其中一个产品族
- 属于同一个产品族的产品被设计成一起使用,且必须保证一致性
- 产品等级结构相对稳定,不会频繁新增产品类型
- 需要提供一个产品类库,但只想显示接口而隐藏实现
不适合使用的场景:
- 产品等级结构会频繁变化(经常新增产品类型)
- 产品之间没有配套使用的约束
- 只有一个产品族,没有切换需求
- 简单的对象创建场景
十一、面试官高频面试题
面试题1:简单工厂、工厂方法、抽象工厂有什么区别?
参考答案:
三者都是创建型模式,核心都是"把对象创建的逻辑封装起来,让客户端不直接依赖具体类",但适用场景和复杂度不同:
| 维度 | 简单工厂 | 工厂方法 | 抽象工厂 |
|---|---|---|---|
| 产品数量 | 一个工厂创建多种产品 | 一个工厂创建一种产品 | 一个工厂创建一族产品 |
| 工厂接口 | 一个带参数的创建方法 | 一个无参数的创建方法 | 多个创建方法(每种产品一个) |
| 开闭原则 | 违反(新增产品改工厂) | 符合(新增产品加工厂) | 倾斜性符合(新增产品族容易,新增产品等级困难) |
| 核心解决的问题 | 封装创建逻辑,避免直接new | 解耦创建与使用,支持扩展 | 保证产品族一致性,支持整体切换 |
| 复杂度 | 低 | 中 | 高 |
一句话总结:简单工厂是"一个工厂造所有东西",工厂方法是"每个东西有自己的工厂",抽象工厂是"一个工厂造一整套配套的东西"。
面试题2:什么是产品族和产品等级?抽象工厂模式如何处理这两个概念?
参考答案:
- 产品等级结构:同一类产品的继承结构,是纵向的。比如"按钮"是抽象产品,"Windows按钮"、"Mac按钮"、"Linux按钮"是具体产品,它们构成一个产品等级结构。
- 产品族:同一个具体工厂生产的、位于不同产品等级结构中的一组产品,是横向的。比如"Windows产品族"包含Windows按钮、Windows文本框、Windows复选框。
抽象工厂模式中:
- 每个抽象产品对应一个产品等级结构
- 每个具体工厂对应一个产品族
- 抽象工厂接口中的每个创建方法对应一个产品等级
新增产品族(新增一个平台)只需新增具体工厂,符合开闭原则;新增产品等级(新增一种组件)需要修改所有工厂,违反开闭原则。这就是"倾斜性开闭原则"。
面试题3:抽象工厂模式的优缺点是什么?
参考答案:
优点:
- 保证产品族的一致性,避免混搭使用导致的错误
- 切换产品族只需替换工厂,业务代码不变
- 客户端与具体实现解耦,符合依赖倒置原则
- 产品族内部的约束关系封装在工厂中,客户端无需关心
- 新增产品族符合开闭原则
缺点:
- 新增产品等级困难,需要修改所有工厂(倾斜性开闭原则)
- 类的数量多,系统复杂度高
- 多层抽象,理解成本高
- 产品族单一或无配套约束时,属于过度设计
面试题4:抽象工厂模式和工厂方法模式的根本区别是什么?
参考答案:
根本区别在于处理的产品等级数量不同:
- 工厂方法模式只处理一个产品等级结构,抽象工厂接口只有一个创建方法。它关注的是"一种产品的不同实现"。
- 抽象工厂模式处理多个产品等级结构,抽象工厂接口有多个创建方法。它关注的是"一族配套产品的整体创建和一致性保证"。
另外,工厂方法模式通过继承来实现创建(子类重写工厂方法),而抽象工厂模式通过组合来实现创建(具体工厂组合了多个产品的创建逻辑)。
面试题5:什么时候该用抽象工厂模式,什么时候不该用?
参考答案:
该用的场景:
- 系统中有多个产品族,且每次只使用其中一个
- 同族产品必须一起使用,有一致性约束
- 产品等级结构稳定,不会频繁新增产品类型
- 需要隐藏产品创建和表示的细节
- 需要支持整体切换(如换平台、换主题、换数据库)
不该用的场景:
- 只有一个产品族,没有切换需求
- 产品之间没有配套使用的约束
- 产品等级结构会频繁变化
- 简单的对象创建,用简单工厂或直接new更合适
面试题6:抽象工厂模式如何解决"开闭原则的倾斜性"问题?
参考答案:
抽象工厂模式的"倾斜性开闭原则"是指:对产品族的扩展开放,对产品等级的扩展关闭。新增产品族容易,新增产品等级困难。
缓解方案:
- 在设计初期充分评估产品等级的稳定性,如果产品等级会频繁变化,不适合用抽象工厂模式
- 使用配置化和反射机制,把创建方法做成可配置的,新增产品等级时通过配置而非修改代码
- 结合其他模式,如使用原型模式注册产品原型,或使用建造者模式构建复杂产品
- 使用默认实现,在抽象工厂中为新增方法提供默认实现(如返回None或抛出不支持异常),减少对已有工厂的影响
但根本上,这个问题是抽象工厂模式的固有特性,无法完全消除,只能缓解。因此选择该模式时要慎重评估产品等级的稳定性。
面试题7:请用Python实现一个抽象工厂模式的例子。
参考答案:
(面试官希望看到完整的代码结构,包括抽象产品、具体产品、抽象工厂、具体工厂、客户端。可以参考本文第七节的跨平台UI实现,或第九节的多云存储实现。)
关键要点:
- 使用
abc.ABC和@abstractmethod定义抽象基类 - 抽象工厂有多个创建方法
- 具体工厂实现所有创建方法,返回配套的具体产品
- 客户端只依赖抽象接口
面试题8:抽象工厂模式在实际框架中有哪些应用?
参考答案:
- Java Swing :不同平台的UI组件使用了类似抽象工厂的思想,
LookAndFeel可以看作抽象工厂 - JDBC :
Connection创建Statement、PreparedStatement,同一连接创建的语句对象天然配套,类似于产品族 - JDK XML解析 :
DocumentBuilderFactory、TransformerFactory等工厂类,不同解析器提供商对应不同的具体工厂 - Spring框架 :
BeanFactory和各种FactoryBean在某些场景下体现了抽象工厂的思想 - ORM框架:如MyBatis、Hibernate,不同数据库方言(Dialect)对应不同的产品族,包含分页SQL、关键字映射等配套产品
- 日志框架:SLF4J作为抽象层,不同的实现(Logback、Log4j2)对应不同的产品族
十二、总结
12.1 核心思想回顾
抽象工厂模式的本质是:用一个"超级工厂"来管理一组相关产品的创建,确保这些产品被一起使用,并且可以整体切换。
它解决的核心问题是:当一组产品有配套约束、且需要在多组产品之间切换时,如何保证一致性并降低切换成本。
12.2 关键概念速记
- 产品族:横向,同一品牌/平台下的多种配套产品
- 产品等级:纵向,同一种产品的不同实现/品牌
- 抽象工厂:声明多个创建方法,每个对应一个产品等级
- 具体工厂:实现抽象工厂,生产一个完整产品族
- 倾斜性开闭原则:新增产品族容易,新增产品等级困难
12.3 使用决策流程

需要创建对象吗?
├─ 只有一种产品,创建逻辑简单 → 直接new或简单工厂
├─ 一种产品,多种实现,需要扩展 → 工厂方法模式
├─ 多种产品,需要配套使用,且有多个产品族 → 抽象工厂模式
└─ 产品等级会频繁变化 → 考虑其他模式(建造者、原型等)
12.4 一句话记住抽象工厂
抽象工厂就是"品牌总代理",你说要哪个品牌,它给你配齐一整套,保证不串味,换品牌也只需要说一声。
本文为原创文章,如需转载,请联系作者获得授权,并注明出处。