⑪ 路由器 + 可插拔后端:让「同一个操作有 N 种实现」不绑架你的代码
目标:学一个通用、可迁移的架构套路——当你系统里"同一个操作有好几种实现可选"时,怎么封装成一个路由器 + 一堆可插拔后端,让调用方永远只认一个统一接口、完全不耦合到某个具体实现。yt-dlp 的网络层用这套支撑了 urllib / requests / curl_cffi 等多个 HTTP 库。
呼应文档 ①③⑧ 的可插拔/解耦思维。①讲"网站不同写不同类",⑧讲"加工不同写不同 PP",这篇讲"同一件事能有多种底层实现,还随便换"。
一、问题:写死一个实现,换库就散架
假设你要发 HTTP 请求,一开始图省事,全用 requests:
import requests
def download(url):
return requests.get(url)
哪天遇到反爬,要用 curl_cffi 伪装浏览器;哪天又要求零依赖,得退回自带的 urllib。于是你的代码变成:
if use_curl: return curl_cffi.get(url)
elif use_urllib: return urllib.request.urlopen(url)
else: return requests.get(url)
散架了:每个调用点都要写这些 if/else;以后再加一个 HTTP 库(比如 httpx),又得全改。调用方(提取器、下载器)被绑架在了具体库上——这就是「耦合」。
二、解法:路由器 + 可插拔后端
yt-dlp 的做法是引入两个角色:
| 角色 | 比喻 | 职责 |
|---|---|---|
| RequestDirector(路由器) | 总机 | 收到请求 → 决定分给哪个后端 |
| RequestHandler(后端) | 各分部话务员 | urllib / requests / curl_cffi 各一个,真正收发请求 |
调用方从此只做一个动作:
response = youtube_dl.urlopen(request) # 交请求、收响应
它不需要也不想知道底下是 urllib 还是 curl_cffi 在干活。
[!important] 心智模型一句话:路由器是总机,后端是可插拔的插头,Request/Response 是统一信封。 调用方只碰总机和信封,从不碰具体插头。
每个后端都是"插头"——声明好自己支持什么(下文的 _SUPPORTED_*),插进路由器就行。想加一个 httpx 后端?新写一个插头注册进去,只要它认统一的 Request/Response,调用方一行都不用改。
三、为什么这套好(3 个收益)
| # | 收益 | 具体 |
|---|---|---|
| 1 | 调用方解耦 | 所有调用只认 urlopen(request),换库不改调用点 |
| 2 | 加实现像装插头 | 新增一个后端 = 新写一个插头 + 注册,核心不动 |
| 3 | 可按场景自动选 | 路由器还能"挑最合适的"(见文档 ⑫ 的两层选择) |
四、在哪用得上(不只是 HTTP 请求)
| 你的场景 | 套用这套 |
|---|---|
| 多渠道支付 | 支付宝/微信/银联各一后端,统一 pay(order),换渠道不动业务代码 |
| 多云存储 | AWS/阿里云/本地盘各一后端,统一 upload(file) |
| 多语言翻译 | Google/DeepL/离线引擎各一后端 |
| 消息/事件推送 | 短信/邮件/Webhook/推送各一后端 |
通用规律:凡是「同一个操作、可能有好几种实现、且以后想自由切换」的能力,都值得封装成 路由器 + 后端注册:调用方永远只依赖那个统一接口。
五、核心金句
别让调用方依赖某个具体实现。 给"同一操作的多实现"套一个路由器 + 可插拔后端:每个实现是一个插头,注册进路由器,调用方只认
router.send(request)这个唯一入口。从此 加实现 = 装新插头,换实现 = 换插头,调用方永不变。
六、在 yt-dlp 里对应哪里(供回溯)
- 路由器:
yt_dlp/networking/common.py的RequestDirector(send()分发) - 后端插头:
RequestHandler子类 +@register_rh注册装饰器 - 统一信封:
Request/Response - 学习库 → [[Networking-Layer]]
七、一句话带走
做系统时,只要发现"某个操作以后可能有多种实现",就别直接
import某个库写死。先定义一个统一接口,每个实现做成可插拔后端注册进路由器,调用方只调那个统一入口。这样你的代码永远不被某个实现绑架——想换、想加、想按场景挑,都只用动插头那一层。
配套:路由器怎么"挑最合适的后端"?→ 文档 ⑫(两层选择)。