FastAPI + 设计模式 —— 从基础到实战
文档版本:1.0
目标读者:有 Python 基础,想系统理解设计模式在 FastAPI 中如何落地的后端开发者。
阅读方式:按章节顺序阅读,每章依赖前一章的知识。
第一章:背景与整体架构
1.1 什么是设计模式
定义:设计模式是针对软件设计中特定问题的可复用解决方案。它不是可以直接复制的代码,而是一种经验总结出的"模板"或"范式"。
设计模式按用途分为三大类:
| 分类 | 解决什么问题 | 常见模式 |
|---|---|---|
| 创建型(Creational) | 对象如何创建 | 工厂模式、单例模式、建造者模式 |
| 结构型(Structural) | 类和对象如何组合成更大的结构 | 适配器模式、装饰器模式、代理模式 |
| 行为型(Behavioral) | 对象之间如何交互和分配职责 | 策略模式、观察者模式、责任链模式 |
为什么需要设计模式:
- 应对变化:软件需求不断变化,设计模式将"变"的部分与"不变"的部分分离。
- 解耦:减少模块间的直接依赖,使修改影响范围可控。
- 复用经验:前人总结的成熟方案,避免重复踩坑。
- 提高可读性:使用模式名称本身就是一种文档,团队成员能快速理解代码意图。
注意:设计模式不是银弹。过度设计或在不合适的场景使用,会增加不必要的复杂度。实践中遵循"三次原则"——同一类问题出现三次以上再考虑抽象。
1.2 什么是 FastAPI
定义:FastAPI 是一个现代 Python Web 框架,用于构建 RESTful API。
核心特性:
- 异步支持:基于 Starlette(ASGI 框架),支持 async/await。
- 自动生成文档:基于 OpenAPI 标准,自动生成 Swagger UI 和 ReDoc。
- 依赖注入系统:通过 Depends 机制,将依赖(如数据库会话、服务对象)从业务逻辑中解耦。
- 类型提示驱动:使用 Python 类型提示定义请求/响应模型,自动完成校验和序列化。
为什么选择 FastAPI:
- 对设计模式的表达能力优于 Flask 等传统框架。
- 依赖注入机制天然支持"面向接口编程"。
- 本文讨论的所有设计模式都可以在 FastAPI 中落地。
1.3 本文的业务场景说明
为了具体展示设计模式的应用,本文统一使用支付渠道对接作为贯穿全文的业务场景。
为什么选择支付场景:
- 支付渠道(支付宝、微信、PayPal 等)变化频繁,不同渠道的参数和逻辑差异大。
- 新增或变更渠道是常见的产品需求,天然需要"对扩展开放,对修改关闭"。
- 该场景足够简单(核心逻辑是 pay(amount)),便于聚焦设计模式本身,而非业务复杂性。
1.4 三层关系总览
graph TD
subgraph 路由层
API["/pay POST
接收请求 → 返回响应"]
end
subgraph 依赖注入层
DI["Depends(get_payment_factory)
提供工厂实例"]
end
subgraph 工厂层
Factory["PaymentFactory
根据 channel 创建具体产品"]
Registry["_registry
渠道 → 类 映射表"]
end
subgraph 产品层
Alipay["Alipay
支付宝支付逻辑"]
WechatPay["WechatPay
微信支付逻辑"]
end
API --> DI --> Factory --> Registry --> Alipay & WechatPay
阅读说明:
- 第一章是语法前提,熟悉 OOP 基础的可快速浏览。
- 第二章是核心,重点理解工厂模式和单例模式的"是什么、为什么、怎么做"。
- 第三章是将设计模式接入 FastAPI 框架。
第二章:Python OOP 基础(设计模式的语法前提)
本章为后续设计模式的实现提供语法铺垫。所有示例均围绕支付场景展开,方便与后续章节衔接。
2.1 什么是类,什么是实例
定义:
- 类(Class):一组对象的蓝图或模板,定义了数据(属性)和行为(方法)。
- 实例(Instance):根据类创建的具体对象,每个实例拥有独立的内存空间。
类属性 vs 实例属性:
- 类属性:定义在类体中、方法之外。属于类本身,所有实例共享。
- 实例属性:定义在 init 方法中,通过 self.xxx 赋值。属于具体实例,每个实例独有。
# 类定义(蓝图)
class Payment:
# 类属性:所有实例共享
currency: str = "CNY"
def __init__(self, amount: float):
# 实例属性:每个实例独有
self.amount = amount
# 实例化
p1 = Payment(100.0)
p2 = Payment(200.0)
# 类属性通过类或实例访问
print(Payment.currency) # CNY
print(p1.currency) # CNY(实例无同名属性时向上查找类)
# 实例属性只能通过实例访问
print(p1.amount) # 100.0
print(Payment.amount) # AttributeError(类没有实例属性)
关键点:init 在实例化时执行,作用是将传入的参数挂载到当前实例上。它不是"构造函数",而是"初始化方法"。真正的构造由 new 完成,本章第四节会涉及。
2.2 什么是继承
定义:继承是一种类之间的关系,子类(Subclass)从父类(Superclass)获得属性和方法,并可以添加或覆盖(Override)自己的实现。
super() 的作用:在子类中调用父类的方法。最常见的是在子类的 init 中调用父类的 init,确保父类中定义的实例属性被正确初始化。
class BasePayment:
def __init__(self, amount: float):
self.amount = amount
def pay(self) -> str:
return f"支付 {self.amount} 元"
class Alipay(BasePayment):
def __init__(self, amount: float, app_id: str):
# 调用父类 __init__,将 amount 挂到当前实例
super().__init__(amount)
self.app_id = app_id
def pay(self) -> str:
# 调用父类方法并扩展
base = super().pay()
return f"{base},使用支付宝"
payment = Alipay(100.0, "app_123")
print(payment.pay()) # 支付 100.0 元,使用支付宝
两种调用父类方式的对比:
# 方式一:super() —— 推荐
super().__init__(amount) # 自动传递 self
# 方式二:通过类名显式调用 —— 不推荐
BasePayment.__init__(self, amount) # 必须手动传 self
为什么推荐 super():
- 不需要显式传递 self,减少错误。
- 在多继承场景下,super() 遵循 MRO(方法解析顺序),比固定类名更安全。
- 父类名称变更时,子类无需修改。
- super() 不限于 init:可以调用父类的任何方法(super().pay()、super().validate() 等)。
2.3 什么是多继承与 Mixin
定义:
- 多继承:一个子类可以同时继承多个父类。Python 支持多继承。
- Mixin:一种特殊的多继承用法。Mixin 类提供可复用的方法,但不设计为独立实例化。它通常不包含 init 方法,只封装行为。
Mixin 规则:
- 不写 init,避免构造方法传参混乱。
- 只封装方法,假设 self 已经具备所需属性(由主类提供)。
- 命名通常以 Mixin 结尾。
# 主基类:定义核心属性
class BasePayment:
def __init__(self, amount: float):
self.amount = amount
# Mixin 类:只封装方法,不写 __init__
class RetryMixin:
def retry_pay(self, times: int = 3) -> str:
for i in range(times):
result = self.pay() # 假设 self 有 pay() 方法
if "成功" in result:
return result
return "支付失败:重试次数用尽"
# 主类:同时继承主基类和 Mixin
class Alipay(BasePayment, RetryMixin):
def pay(self) -> str:
return f"支付宝支付 {self.amount} 元"
payment = Alipay(100.0)
print(payment.retry_pay()) # 支付宝支付 100.0 元
何时使用 Mixin:
- 需要为多个不相关的类添加相同的能力(如日志、重试、缓存)。
- 这些能力不改变类的核心身份(Alipay 的本质是支付,不是"可重试支付")。
2.4 什么是抽象方法
定义:抽象方法是在父类中声明但不实现的方法,由子类必须重写实现。@abstractmethod 是 Python 的 abc 模块提供的装饰器。
作用:
- 定义"契约":父类规定子类必须提供某个能力,但不关心具体实现。
- 实例化时校验(Fail-Fast):如果子类没有重写抽象方法,实例化时会立即报错,而非等到运行时才暴露。
from abc import ABC, abstractmethod
# 抽象基类:定义了契约
class Payment(ABC):
@abstractmethod
def pay(self, amount: float) -> str:
"""子类必须实现此方法"""
pass
# 正确实现:可以实例化
class Alipay(Payment):
def pay(self, amount: float) -> str:
return f"支付宝支付 {amount} 元"
# 错误实现:没有重写 pay()
class WechatPay(Payment):
pass
alipay = Alipay() # 正常
# wechat = WechatPay() # TypeError: Can't instantiate abstract class WechatPay without pay()
在工厂模式中的作用:
- 工厂返回的是抽象类型 Payment,调用者只关心 payment.pay() 是否存在。
- @abstractmethod 保证了所有具体支付类都实现了 pay(),工厂不会生产出"哑巴"产品。
第三章:工厂模式(Factory Pattern)
3.1 什么是工厂模式
定义:工厂模式是一种创建型设计模式,它将对象的创建逻辑封装到独立的工厂类或工厂方法中,调用方通过工厂获取对象,而无需直接 new 具体类。
分类:
- 简单工厂(Simple Factory):一个工厂类根据传入参数决定创建哪个具体产品。本文使用此模式。
- 工厂方法(Factory Method):定义一个创建对象的接口,由子类决定实例化哪个类。
- 抽象工厂(Abstract Factory):创建一系列相关或依赖的对象,不指定具体类。
为什么需要工厂模式:
- 调用方不需要知道具体类的构造细节(如支付宝需要 app_id 和 private_key)。
- 新增或修改具体类时,只需修改工厂,调用方代码不变。
- 便于单元测试(可以注入 Mock 工厂)。
3.2 没有工厂的代码什么样
from typing import Literal
# 具体支付类
class Alipay:
def __init__(self, app_id: str, private_key: str):
self.app_id = app_id
self.private_key = private_key
def pay(self, amount: float) -> str:
return f"支付宝支付 {amount} 元"
class WechatPay:
def __init__(self, mch_id: str, api_key: str):
self.mch_id = mch_id
self.api_key = api_key
def pay(self, amount: float) -> str:
return f"微信支付 {amount} 元"
# 业务函数:直接依赖具体类
def process_payment(channel: str, amount: float) -> str:
if channel == "alipay":
payment = Alipay("app_123", "key_456")
elif channel == "wechat":
payment = WechatPay("mch_789", "key_012")
else:
raise ValueError(f"不支持: {channel}")
return payment.pay(amount)
# 调用
print(process_payment("alipay", 100.0))
痛点:
- 业务函数与具体类耦合(Alipay、WechatPay 写死在函数体)。
- 新增渠道(如 PayPal)需要修改 process_payment(违反开闭原则)。
- 测试 process_payment 时必须真正实例化支付宝/微信,需要真实的密钥配置。
3.3 使用简单工厂重构
# 抽象产品(可选,但推荐)
from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def pay(self, amount: float) -> str:
pass
# 具体产品
class Alipay(Payment):
def __init__(self, app_id: str, private_key: str):
self.app_id = app_id
self.private_key = private_key
def pay(self, amount: float) -> str:
return f"支付宝支付 {amount} 元"
class WechatPay(Payment):
def __init__(self, mch_id: str, api_key: str):
self.mch_id = mch_id
self.api_key = api_key
def pay(self, amount: float) -> str:
return f"微信支付 {amount} 元"
# 工厂类:封装创建逻辑
class PaymentFactory:
@staticmethod
def create(channel: str) -> Payment:
if channel == "alipay":
return Alipay("app_123", "private_key_456")
elif channel == "wechat":
return WechatPay("mch_789", "api_key_012")
else:
raise ValueError(f"不支持: {channel}")
# 业务函数:只依赖工厂,不依赖具体类
def process_payment(channel: str, amount: float) -> str:
payment = PaymentFactory.create(channel)
return payment.pay(amount)
# 调用
print(process_payment("alipay", 100.0))
收益:
- process_payment 不再依赖 Alipay、WechatPay 具体类。
- 新增渠道只修改 PaymentFactory.create,业务函数不变。
- 测试时可以替换工厂返回 Mock 对象。
3.4 处理构造参数不一致的问题
不同支付渠道的构造参数不同:
- 支付宝:app_id, private_key
- 微信:mch_id, api_key
方案一:args, *kwargs(不推荐)
class PaymentFactory:
@staticmethod
def create(channel: str, *args, **kwargs) -> Payment:
if channel == "alipay":
return Alipay(*args, **kwargs)
elif channel == "wechat":
return WechatPay(*args, **kwargs)
问题:调用方必须知道每个渠道的参数顺序和个数,破坏封装。如果支付宝调整参数,所有调用方都要改。
方案二:Pydantic 参数对象(推荐)
from pydantic import BaseModel
# 统一参数对象(包含所有渠道的可能字段)
class CreatePaymentRequest(BaseModel):
channel: str
amount: float
# 支付宝需要的
app_id: str | None = None
private_key: str | None = None
# 微信需要的
mch_id: str | None = None
api_key: str | None = None
class PaymentFactory:
@staticmethod
def create(req: CreatePaymentRequest) -> Payment:
if req.channel == "alipay":
if not req.app_id or not req.private_key:
raise ValueError("缺少支付宝参数")
return Alipay(req.app_id, req.private_key)
elif req.channel == "wechat":
if not req.mch_id or not req.api_key:
raise ValueError("缺少微信参数")
return WechatPay(req.mch_id, req.api_key)
else:
raise ValueError(f"不支持: {req.channel}")
# 调用方
req = CreatePaymentRequest(
channel="alipay",
amount=100.0,
app_id="app_123",
private_key="key_456"
)
payment = PaymentFactory.create(req)
print(payment.pay(req.amount))
收益:
- 调用方只需要构造一个 Pydantic 对象,参数由 FastAPI 自动解析。
- 参数校验集中管理,工厂负责检查参数完整性。
- 新增渠道只需在模型中增加字段,调用方代码不改。
方案三:工厂方法模式(消除 if-else)
from typing import Type, Dict
class Payment(ABC):
@abstractmethod
def pay(self, amount: float) -> str: pass
@staticmethod
@abstractmethod
def from_request(req: CreatePaymentRequest) -> "Payment":
pass
class Alipay(Payment):
def __init__(self, app_id: str, private_key: str):
self.app_id = app_id
self.private_key = private_key
def pay(self, amount: float) -> str:
return f"支付宝支付 {amount} 元"
@staticmethod
def from_request(req: CreatePaymentRequest) -> "Payment":
if not req.app_id or not req.private_key:
raise ValueError("缺少支付宝参数")
return Alipay(req.app_id, req.private_key)
class WechatPay(Payment):
def __init__(self, mch_id: str, api_key: str):
self.mch_id = mch_id
self.api_key = api_key
def pay(self, amount: float) -> str:
return f"微信支付 {amount} 元"
@staticmethod
def from_request(req: CreatePaymentRequest) -> "Payment":
if not req.mch_id or not req.api_key:
raise ValueError("缺少微信参数")
return WechatPay(req.mch_id, req.api_key)
class PaymentFactory:
_registry: Dict[str, Type[Payment]] = {
"alipay": Alipay,
"wechat": WechatPay,
}
@staticmethod
def create(req: CreatePaymentRequest) -> Payment:
cls = PaymentFactory._registry.get(req.channel)
if cls is None:
raise ValueError(f"不支持: {req.channel}")
# cls 是类引用(内存地址),不是实例
# cls.from_request(req) 才真正创建实例
return cls.from_request(req)
关键点:
- cls = _registry.get("alipay") 返回的是类 Alipay 的引用(类似门牌号),不是实例。
- cls.from_request(req) 调用类的静态方法,内部完成实例化。
- 新增渠道:写一个新类 + 注册到 _registry,工厂代码本身不修改。
类图:
classDiagram
class Payment {
<>
+pay(amount: float) str*
+from_request(req) Payment*
}
class Alipay {
-app_id: str
-private_key: str
+pay(amount: float) str
+from_request(req) Payment
}
class WechatPay {
-mch_id: str
-api_key: str
+pay(amount: float) str
+from_request(req) Payment
}
class PaymentFactory {
-_registry: Dict
+create(req) Payment
}
Payment <|-- Alipay
Payment <|-- WechatPay
PaymentFactory ..> Payment : creates
PaymentFactory ..> CreatePaymentRequest : uses
第四章:单例模式(Singleton Pattern)
4.1 什么是单例模式
定义:单例模式确保一个类有且只有一个实例,并提供一个全局访问点。
适用场景:
- 数据库连接池(避免频繁创建和销毁连接)
- 配置中心(全局统一配置)
- 日志器(统一日志输出)
- 缓存对象(如 Redis 客户端)
为什么需要单例:
- 某些资源创建成本高,重复创建浪费内存和性能。
- 某些状态必须全局一致(如配置项),多实例会导致数据不同步。
4.2 单例模式的标准实现
class DatabaseConnection:
_instance = None # 存储唯一实例的类属性
_initialized = False # 防止 __init__ 重复执行
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not DatabaseConnection._initialized:
self.connection_string = "postgresql://localhost:5432/mydb"
print("数据库连接初始化(仅一次)")
DatabaseConnection._initialized = True
def query(self, sql: str) -> str:
return f"执行: {sql}"
# 验证
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True
print(id(db1) == id(db2)) # True
执行流程:
- 第一次调用 DatabaseConnection():
- new 检查 _instance 为空 → 创建新实例 → 存入 _instance
- init 检查 _initialized 为 False → 执行初始化 → 设为 True
- 第二次调用:
- new 检查 _instance 非空 → 直接返回已有实例
- init 检查 _initialized 为 True → 跳过初始化
注意:init 每次都会被调用,所以必须通过标记防止重复初始化。
4.3 常见误区
# 普通多实例(不是单例)
db1 = DatabaseConnection() # 实例 A
db2 = DatabaseConnection() # 实例 B(不同内存地址)
# 如果不重写 __new__,每次都是新实例
print(db1 is db2) # False
区分:
- "只创建了一个实例"是偶然的(代码里只写了一次 DatabaseConnection())。
- 单例模式是强制性的,无论在代码中调用多少次,都返回同一个实例。
面试追问:单例模式在分布式系统中还成立吗?
不成立。单例模式保证的是"单个进程内"的唯一实例。
在分布式环境中(多个服务实例),每个进程各有自己的单例,需要借助 Redis 或 Zookeeper 等外部协调服务实现全局单例。
4.4 工厂模式 vs 单例模式对比
| 维度 | 工厂模式 | 单例模式 |
|---|---|---|
| 定义 | 封装对象的创建逻辑 | 确保类只有一个实例 |
| 解决什么问题 | 解耦调用方和具体类 | 全局统一资源、节省内存 |
| 实现方式 | 独立的 create 方法 | 重写 new,控制 _instance |
| 每次调用的结果 | 通常返回新实例 | 始终返回同一个实例 |
| 适用场景 | 支付渠道创建、数据源切换 | 数据库连接池、配置中心 |
两者可以组合:工厂本身可以是单例,但工厂生产的产品通常是新实例。
class PaymentFactory:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def create(self, channel: str) -> Payment:
# 每次返回新实例
if channel == "alipay":
return Alipay(...)
第五章:FastAPI 集成实战
5.1 什么是依赖注入
定义:依赖注入是一种设计原则,对象的依赖(即它需要的外部资源)由外部创建并"注入"到该对象中,而不是由对象自己创建。
FastAPI 的 Depends 机制:
- 在路径操作函数(路由)的参数中使用 Depends,声明该参数由框架在运行时解析。
- FastAPI 会执行 Depends 指定的函数,并将返回值注入到参数中。
- 支持异步、支持依赖链(依赖可以依赖其他依赖)。
为什么需要 Depends:
- 路由层不负责创建依赖对象,只声明"我需要什么"。
- 依赖的生命周期由框架管理,便于测试时替换。
- 代码更干净,职责边界清晰。
5.2 将工厂作为依赖注入
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel
app = FastAPI()
# 请求模型
class PayRequest(BaseModel):
channel: str
amount: float
app_id: str | None = None
private_key: str | None = None
mch_id: str | None = None
api_key: str | None = None
# 响应模型
class PayResponse(BaseModel):
result: str
# 依赖函数:返回工厂实例
def get_payment_factory() -> PaymentFactory:
return PaymentFactory()
# 路由:通过 Depends 注入工厂
@app.post("/pay", response_model=PayResponse)
def pay(
req: PayRequest,
factory: PaymentFactory = Depends(get_payment_factory)
):
try:
# 将请求转换为创建参数(使用工厂方法模式)
payment = factory.create(req)
result = payment.pay(req.amount)
return PayResponse(result=result)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
路由层的职责:
- 接收 HTTP 请求 → 解析为 PayRequest 模型。
- 声明需要 PaymentFactory(由 Depends 注入)。
- 调用工厂创建支付对象 → 执行业务逻辑 → 返回响应。
- 没有任何 if-else,没有任何具体类名(Alipay、WechatPay 不出现在路由中)。
5.3 将单例模式与 Depends 结合
在 FastAPI 中,通常不手动重写 new,而是利用框架的能力实现单例。
方案一:使用 @lru_cache
from functools import lru_cache
class DatabaseConnection:
def __init__(self):
self.connection_string = "postgresql://localhost:5432/mydb"
print("数据库连接初始化")
def query(self, sql: str) -> str:
return f"执行: {sql}"
@lru_cache
def get_db() -> DatabaseConnection:
# 此函数只会执行一次,后续调用返回缓存结果
return DatabaseConnection()
@app.get("/users")
def get_users(db: DatabaseConnection = Depends(get_db)):
return {"data": db.query("SELECT * FROM users")}
@lru_cache 的机制:首次调用 get_db() 时执行函数体并缓存返回值;后续调用直接返回缓存中的对象,实现了单例效果。
方案二:使用全局变量
_db_instance = None
def get_db() -> DatabaseConnection:
global _db_instance
if _db_instance is None:
_db_instance = DatabaseConnection()
return _db_instance
方案一更简洁,方案二更显式。两者均可。
5.4 完整路由代码(含所有模型)
from fastapi import FastAPI, Depends, HTTPException
from pydantic import BaseModel
from typing import Literal
from abc import ABC, abstractmethod
from functools import lru_cache
# ========== 1. 抽象产品 ==========
class Payment(ABC):
@abstractmethod
def pay(self, amount: float) -> str:
pass
@staticmethod
@abstractmethod
def from_request(req: "PayRequest") -> "Payment":
pass
# ========== 2. 具体产品 ==========
class Alipay(Payment):
def __init__(self, app_id: str, private_key: str):
self.app_id = app_id
self.private_key = private_key
def pay(self, amount: float) -> str:
return f"支付宝支付 {amount} 元"
@staticmethod
def from_request(req: "PayRequest") -> Payment:
if not req.app_id or not req.private_key:
raise ValueError("缺少支付宝参数")
return Alipay(req.app_id, req.private_key)
class WechatPay(Payment):
def __init__(self, mch_id: str, api_key: str):
self.mch_id = mch_id
self.api_key = api_key
def pay(self, amount: float) -> str:
return f"微信支付 {amount} 元"
@staticmethod
def from_request(req: "PayRequest") -> Payment:
if not req.mch_id or not req.api_key:
raise ValueError("缺少微信参数")
return WechatPay(req.mch_id, req.api_key)
# ========== 3. 工厂 ==========
class PaymentFactory:
_registry = {
"alipay": Alipay,
"wechat": WechatPay,
}
def create(self, req: "PayRequest") -> Payment:
cls = self._registry.get(req.channel)
if cls is None:
raise ValueError(f"不支持: {req.channel}")
return cls.from_request(req)
# ========== 4. FastAPI 应用 ==========
app = FastAPI()
class PayRequest(BaseModel):
channel: Literal["alipay", "wechat"]
amount: float
app_id: str | None = None
private_key: str | None = None
mch_id: str | None = None
api_key: str | None = None
class PayResponse(BaseModel):
result: str
trace_id: str | None = None
# 依赖注入:工厂本身作为单例
@lru_cache
def get_payment_factory() -> PaymentFactory:
return PaymentFactory()
@app.post("/pay", response_model=PayResponse)
def pay(
req: PayRequest,
factory: PaymentFactory = Depends(get_payment_factory)
) -> PayResponse:
try:
payment = factory.create(req)
result = payment.pay(req.amount)
return PayResponse(result=result)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
第六章:完整流程串联
6.1 从 HTTP 请求到支付执行
6.2 各层的职责边界
| 层级 | 职责 | 不做什么 |
|---|---|---|
| 路由层(/pay) | HTTP 请求/响应处理 | 不创建支付对象,不做业务逻辑 |
| 依赖注入层(Depends) | 提供工厂实例 | 不创建具体支付类 |
| 工厂层(PaymentFactory) | 根据 channel 创建具体支付对象 | 不执行业务逻辑 |
| 具体支付类(Alipay) | 执行具体支付逻辑 | 不关心自己如何被创建 |
第七章:下一步学习计划
7.1 已覆盖的核心概念
| 分类 | 具体内容 |
|---|---|
| Python OOP 基础 | 类与实例、类属性/实例属性、继承与 super、Mixin、抽象方法 |
| 创建型设计模式 | 工厂模式(简单工厂 + 工厂方法)、单例模式 |
| FastAPI 集成 | 依赖注入 Depends、@lru_cache 实现单例、Pydantic 模型 |
| 代码组织 | 抽象产品 → 具体产品 → 工厂 → 路由分层 |
7.2 建议下一步学习的知识
| 分类 | 具体内容 | 与本文的关系 |
|---|---|---|
| 策略模式 | 将"算法"(如不同渠道的手续费计算)封装为可互换的对象 | 与工厂模式互补,工厂负责"创建",策略负责"行为" |
| 仓库模式 | 将数据访问逻辑封装到独立的 Repository 层 | 进一步解耦业务逻辑与数据库操作 |
| 观察者模式 | 通过事件驱动实现异步解耦(如支付成功发通知) | 解决跨模块通信问题 |
| 中间件 | FastAPI 的中间件机制(日志、限流、鉴权) | 横切关注点与业务逻辑分离 |
| BackgroundTasks | FastAPI 的异步后台任务 | 支付成功后的异步处理(邮件通知、积分更新) |
| pytest + 依赖注入 | 利用依赖注入替换测试中的 Mock 对象 | 将本文的模式应用于单元测试 |
| Docker | 容器化应用交付 | 将代码固化为可移植的运行环境 |
7.3 学习路径建议
阶段一:巩固当前内容(1 周)
- 手敲第三章所有代码,至少两遍。
- 尝试在 _registry 中注册一个新的支付渠道(如 PayPal)。
- 写单元测试覆盖 PaymentFactory.create() 的三种情况(alipay、wechat、不支持的渠道)。
阶段二:学习策略模式(1 周)
- 理解策略模式的核心思想:封装算法族,使其可互换。
- 将"渠道手续费计算"抽成独立策略,与工厂模式配合。
- 实现一个"动态路由":根据订单金额自动选择最优渠道。
阶段三:学习仓库模式(1 周)
- 将支付记录存储到数据库。
- 定义 PaymentRepository 接口,实现 PostgreSQL 版本。
- 在 Alipay.pay() 中调用仓库保存记录。
阶段四:学习观察者模式 + 异步(1 周)
- 支付成功后发布事件 PaymentSuccessEvent。
- 编写监听器:发送邮件、记录审计日志、更新用户积分。
- 使用 FastAPI 的 BackgroundTasks 实现异步执行。
阶段五:容器化交付(1 周)
- 编写 Dockerfile 将 FastAPI 应用打包。
- 使用 docker-compose 编排 FastAPI + PostgreSQL + Redis。
- 配置环境变量注入工厂参数(如切换支付渠道的密钥)。
7.4 推荐资源
书籍:
- 《设计模式:可复用面向对象软件的基础》(GoF 经典,建议阅读工厂模式、策略模式、观察者模式章节)
- 《Python 设计模式》(适合 Python 开发者)
官方文档:
- FastAPI 官方文档(重点阅读 Dependencies 章节)
- Python abc 模块文档
- Pydantic 官方文档
实操建议:
- 不要一口气学完所有模式,每学一个模式就在自己的 Demo 项目中跑一遍。
- 阅读开源项目源码(如 FastAPI 本身、SQLAlchemy),观察它们在哪些地方使用了设计模式。
附录:本文全部代码的目录结构
project/
├── main.py # FastAPI 入口
├── models.py # Pydantic 请求/响应模型
├── dependencies.py # 依赖注入函数(get_payment_factory、get_db)
├── payments/
│ ├── __init__.py
│ ├── base.py # Payment 抽象类
│ ├── alipay.py # Alipay 具体实现
│ ├── wechat.py # WechatPay 具体实现
│ └── factory.py # PaymentFactory
└── tests/
├── __init__.py
├── test_factory.py # 工厂模式单元测试
└── test_api.py # FastAPI 集成测试
No comments yet. Be the first!