type
status
date
slug
summary
tags
category
icon
password
Bridge Pattern问题介绍什么是 Bridge Pattern为何采用 Bridge Pattern结构主要应用场景日常工作中的实践案例分析 - Python: Logger ↔ Handler案例分析 - Havok: Fetcher ↔ Analyzer问题讨论参考资料
Bridge Pattern
问题
- 什么是 Bridge Pattern (桥接模式) ?
- 为何要采用 Bridge Pattern?
- 工作中有哪些场景、库用到了 Bridge Pattern
介绍
什么是 Bridge Pattern
桥接模式是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。

为何采用 Bridge Pattern
- 假设我们要开发一个跨平台系统控制程序
Controller,可调节系统的音量volumeUp()volumeDown(),能够满足 Linux 和 Windows 同时使用
- 最简单的实现方式:创建 2个类(如
LinuxControllerWindowsController),每个系统各对应一个类完成平台特定的操作
- 随着时间推移,出现了一些问题:
- 产品提出新的需求:阅读模式(设置音量、亮度,关闭Wi-Fi)
- 随着 Mac 用户增加,产品决定增加对于 MacOS 系统的支持
- 开发者 os: 🤯🤯🤯
- 分析
- 程序在一开始设计时,未考虑在各个系统间进行解耦;
- 随着时间的推移,系统各部分会逐渐演化,程序的功能随之有所增减;耦合紧密的系统难以应对未来的变化
- 每增加一种新功能 or 系统支持时,

解决方案
- 采用 Bridge Pattern,将程序的功能 & 系统的底层实现 解耦合
- 面向 client 的程序只调用「接口」使用功能,具体实现(尤其平台差异)交由不同系统各自完成
结构
如右图所示,一般 Bridge Pattern 可用此结构表示:
- Abstraction: 抽象部分,提供面向用户操作的接口
- Implementation: 实现系统的接口封装,提供一些方法,用于完成面向用户的复杂功能
- Concrete Implementations: 具体的实现类,可实现平台特定、类型特定的代码
- Refined Abstraction: 可在父类的基础上,重载原有的功能 or 提供更丰富的功能
- Client: 客户代码,与 Abstraction 对象交互,使用其提供的功能

主要应用场景
- 面向复杂的、较大的系统,希望拆分系统为若干个独立的子系统
- 消除平台、系统差异化,对外提供统一的操作接口,如:
- 操作系统的差异:Linux Windows MacOS…
- 数据库类型差异:MySQL PostgreSQL SQLite…
- 文件类型的差异:Yaml Json Toml…
- ……
日常工作中的实践
- 常见的 orm 库 - 隐藏数据库类型差异:
- Python - SQLAlchemy
- Go - gorm
- Python logging 中
Logger Class与Handler的关系
案例分析 - Python: Logger ↔ Handler
- 背景介绍
Logger: 接收 log event,将其处理成LogRecord对象后,发送给Handleror 父级Logger处理Handler: 接收来自Logger Class的LogRecord对象,通过emit()方法对日志做任何想做的事情 (yes, anything! )
- 如何应用 Bridge Pattern
Logger和Handler通过 Bridge Pattern 解耦成 2个互相独立的子系统Handler演化举例:我想把 log 输出到……- 控制台:StreamHandler
- 文件:FileHandler
- 邮件:SMTPHandler
- 不想要了:NullHandler
- …… 官方库中提供了许多预定义的 Handler,参见:python logging.handlers
- 其他自定义的需求(Anything)均可通过自定义
Handler完成

- 代码示例:
Handler需求:将 ERROR 级别的 log 持久化到数据库中- 演示代码:
- 问题:
- 说好的 2种子系统互相独立演变,有没有
Logger Class的例子🌰 Logger Class也提供了一些可重载的接口,如make_record()接口用于定制化构建LogRecord对象- 日常工作接触中,
pytest-reportportal插件中同时重载了Logger Class和Handler,来满足定制化的日志需求
pytest-reportportal插件中的应用- 日志可携带附件,可上传文件、图片等
- 日志通过 API 上传到 RP Server
- 重载
_log()方法,将附件存入LogRecord对象 - 重载
emit()方法,解析logRecord.extra信息;调用 API 完成日志上报
需求 / 功能
rp_logging.RPLogger
rp_logging.RPHandler
案例分析 - Havok: Fetcher ↔ Analyzer
- 全链路压测工具 Havok 中使用了 Bridge Pattern 来解耦
Fetcher和Analyzer
- 背景介绍
Fetcher: 负责对日志数据的索引以及反序列化(LogRecord)Analyzer: 日志分析器,是Fetcher的组件之一,用于将采集的单行日志[]byte反序列化成LogRecordWrapper对象

- 不同的
Fetcher↔ 不同的Analyzer Fetcher面向 client 提供服务, 但需要考虑不同的数据来源(File, SLS, Kafka……)Analyzer对应 Implementation,需应对不同的业务接入,可演化为QrWapPayAnalyzerUPayGatewayAnalyzer等等Fetcher可通过WithAnalyzer(analyzer)方法获取Analyzer对象的引用- code: https://github.com/WoSai/havok/blob/9d2d1593db3edcf683b22b7c21824bac9cec2e26/dispatcher/fetcher.go#L109
- 如此设计下,
Fetcher即可与Analyzer互相独立,各自随着系统演化而变化,而无需修改原有的系统层面代码(这也是 Bridge Pattern 的优点之一)
classDiagram direction TD baseFetcher <|-- fileFetcher baseFetcher <|-- SLSFetcher baseFetcher: - Analyzer analyzer baseFetcher: + Start() baseFetcher: + WithAnalyser() Analyzer <|-- BaseAnalyzer Analyzer <|-- QrWapPayAnalyzer Analyzer <|-- UpayGatewayAnalyzer baseFetcher o-- Analyzer class fileFetcher{ + Start() } class SLSFetcher{ + Start() } class Analyzer{ + Use() + Analyze() } class BaseAnalyzer{ - analysers + Use() + Analyze() } class QrWapPayAnalyzer{ + Analyze() } class UpayGatewayAnalyzer{ + Analyze() }
- A little more…
- 实际使用上,并未采用自定义
Analyzer的方式与Fetcher进行交互,均采用同一个 baseAnalyzer - 通过添加 analyze function 的形式将业务特定的 Analyze Function 注册到
baseAnalyzer中
问题讨论
除了上述例子外,工作中还遇到哪些使用了 Bridge Pattern 的案例?
- 硬件:不同的外设、接口(USB、Type-C)← Bridge → 不同的主板、系统
Strategy (策略) VS Bridge
与其他模式的关联
从不同的角度来看:
- 如何组织这个系统:
- Bridge Pattern 作为结构型模式,首要解决的问题是 类 or 系统的结构问题,复杂的系统可被拆分为独立的子系统,子系统可以互相演变,之间通过“一座桥”(一般是对象引用)来衔接
- 如果原系统代码无法修改,但又希望提供新的能力:Adapter Pattern
- 希望提供访问控制、远程对象的本地代理:Proxy Pattern
- ……
- 如何使用这个系统:
- 考虑采用行为模式:如 Strategy Pattern,在运行时动态调整,更换一个对象引用即可
- 如何创建这个系统:
- 考虑采用创建型模式:如 Abstract Factory,创建不同的对象,组合出想要的“产品”




