分享 - Bridge Pattern
2023-4-13
| 2023-5-22
0  |  0 分钟
type
status
date
slug
summary
tags
category
icon
password

Bridge Pattern

问题

  • 什么是 Bridge Pattern (桥接模式) ?
  • 为何要采用 Bridge Pattern?
  • 工作中有哪些场景、库用到了 Bridge Pattern

介绍

什么是 Bridge Pattern

桥接模式是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。
notion image
 

为何采用 Bridge Pattern

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

结构

如右图所示,一般 Bridge Pattern 可用此结构表示:
  1. Abstraction: 抽象部分,提供面向用户操作的接口
  1. Implementation: 实现系统的接口封装,提供一些方法,用于完成面向用户的复杂功能
  1. Concrete Implementations: 具体的实现类,可实现平台特定、类型特定的代码
  1. Refined Abstraction: 可在父类的基础上,重载原有的功能 or 提供更丰富的功能
  1. Client: 客户代码,与 Abstraction 对象交互,使用其提供的功能
 
notion image

主要应用场景

  • 面向复杂的、较大的系统,希望拆分系统为若干个独立的子系统
  • 消除平台、系统差异化,对外提供统一的操作接口,如:
    • 操作系统的差异:Linux Windows MacOS…
    • 数据库类型差异:MySQL PostgreSQL SQLite…
    • 文件类型的差异:Yaml Json Toml…
    • ……

日常工作中的实践

  • 常见的 orm 库 - 隐藏数据库类型差异:
    • Python - SQLAlchemy
    • Go - gorm
  • Python logging 中 Logger ClassHandler 的关系

案例分析 - Python: Logger ↔ Handler

  • 背景介绍
    • Logger: 接收 log event,将其处理成 LogRecord 对象后,发送给 Handler or 父级 Logger 处理
    • Handler: 接收来自Logger ClassLogRecord对象,通过emit()方法对日志做任何想做的事情 (yes, anything! )
  • 如何应用 Bridge Pattern
    • LoggerHandler 通过 Bridge Pattern 解耦成 2个互相独立的子系统
    • Handler 演化举例:我想把 log 输出到……
      • 控制台:StreamHandler
      • 文件:FileHandler
      • 邮件:SMTPHandler
      • 不想要了:NullHandler
      • …… 官方库中提供了许多预定义的 Handler,参见:python logging.handlers
      • 其他自定义的需求(Anything)均可通过自定义 Handler 完成
notion image
  • 问题:
    • 说好的 2种子系统互相独立演变,有没有Logger Class的例子🌰
    • Logger Class 也提供了一些可重载的接口,如 make_record() 接口用于定制化构建 LogRecord 对象
    • 日常工作接触中,pytest-reportportal 插件中同时重载了 Logger ClassHandler,来满足定制化的日志需求
    •  
 

案例分析 - Havok: Fetcher ↔ Analyzer

  • 全链路压测工具 Havok 中使用了 Bridge Pattern 来解耦 FetcherAnalyzer
  • 背景介绍
    • notion image
    • Fetcher: 负责对日志数据的索引以及反序列化(LogRecord
    • Analyzer: 日志分析器,是Fetcher的组件之一,用于将采集的单行日志[]byte反序列化成LogRecordWrapper对象
  • 不同的 Fetcher ↔ 不同的 Analyzer
    • Fetcher 面向 client 提供服务, 但需要考虑不同的数据来源(File, SLS, Kafka……)
    • Analyzer 对应 Implementation,需应对不同的业务接入,可演化为 QrWapPayAnalyzer UPayGatewayAnalyzer 等等
    • 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() }
 

问题讨论

除了上述例子外,工作中还遇到哪些使用了 Bridge Pattern 的案例?
  • 硬件:不同的外设、接口(USB、Type-C)← Bridge → 不同的主板、系统
Adapter (适配器) VS Bridge
https://unsplash.com/photos/4xnucLdUPtA
Strategy (策略) VS Bridge
 
与其他模式的关联
从不同的角度来看:
  • 如何组织这个系统:
    • Bridge Pattern 作为结构型模式,首要解决的问题是 类 or 系统的结构问题,复杂的系统可被拆分为独立的子系统,子系统可以互相演变,之间通过“一座桥”(一般是对象引用)来衔接
    • 如果原系统代码无法修改,但又希望提供新的能力:Adapter Pattern
    • 希望提供访问控制、远程对象的本地代理:Proxy Pattern
    • ……
  • 如何使用这个系统:
    • 考虑采用行为模式:如 Strategy Pattern,在运行时动态调整,更换一个对象引用即可
  • 如何创建这个系统:
    • 考虑采用创建型模式:如 Abstract Factory,创建不同的对象,组合出想要的“产品”

参考资料

技术分享
  • Design Pattern
  • Notion - ALL-IN-ONE 的神 & 实用技巧分享Notion - ALL-IN-ONE 的神 & 实用技巧分享
    目录