桥接模式 (Bridge Pattern)
一、模式概述
桥接模式的核心理念是:将抽象部分与实现部分分离,使它们可以独立变化。
这句话听起来很抽象,用具体场景来理解:假设你在开发一个图形绘制库,有"形状"(圆形、矩形、三角形)和"渲染器"(OpenGL、DirectX、软件渲染)两个维度。如果用继承,你需要创建 CircleOpenGL、CircleDirectX、CircleSoftware、RectangleOpenGL……类数量是 M×N 的笛卡尔积。每新增一个形状或一个渲染器,都要修改大量类。
桥接模式的解法是:把"形状"和"渲染器"拆成两条独立的继承链,形状持有渲染器的引用,通过组合而非继承连接它们。新增形状不影响渲染器,新增渲染器不影响形状。类数量从 M×N 降为 M+N。
为什么叫"桥接"?
因为两条继承链之间有一座"桥"——抽象类持有一个实现接口的引用。这座桥让两端可以独立演化:
抽象层 实现层
Shape ───────桥─────── Renderer
/ \ / \
Circle Rectangle OpenGL DirectX
二、模式结构
| 角色 | 职责 |
|---|---|
| Abstraction(抽象) | 定义高层接口,持有 Implementor 引用 |
| RefinedAbstraction(扩展抽象) | 扩展 Abstraction 的功能 |
| Implementor(实现接口) | 定义底层操作接口,供 Abstraction 调用 |
| ConcreteImplementor(具体实现) | 实现 Implementor 接口的具体类 |
关键设计原则:Abstraction 不直接创建 ConcreteImplementor,而是通过 Implementor 接口调用。这样 Abstraction 完全不依赖具体实现。
三、Go 语言实现
3.1 形状与渲染器
package main
import "fmt"
// Renderer 实现接口 —— 绘制原语
type Renderer interface {
DrawCircle(x, y, radius int)
DrawRectangle(x, y, width, height int)
}
// ===== 具体实现:不同渲染器 =====
type OpenGLRenderer struct{}
func (r *OpenGLRenderer) DrawCircle(x, y, radius int) {
fmt.Printf(" [OpenGL] 绘制圆形: 中心(%d,%d) 半径=%d\n", x, y, radius)
}
func (r *OpenGLRenderer) DrawRectangle(x, y, w, h int) {
fmt.Printf(" [OpenGL] 绘制矩形: (%d,%d) %dx%d\n", x, y, w, h)
}
type DirectXRenderer struct{}
func (r *DirectXRenderer) DrawCircle(x, y, radius int) {
fmt.Printf(" [DirectX] 绘制圆形: 中心(%d,%d) 半径=%d\n", x, y, radius)
}
func (r *DirectXRenderer) DrawRectangle(x, y, w, h int) {
fmt.Printf(" [DirectX] 绘制矩形: (%d,%d) %dx%d\n", x, y, w, h)
}
// ===== 抽象层:形状 =====
type Shape struct {
renderer Renderer // 桥接的引用
}
func (s *Shape) SetRenderer(r Renderer) {
s.renderer = r
}
type Circle struct {
Shape
x, y, radius int
}
func NewCircle(x, y, radius int) *Circle {
return &Circle{x: x, y: y, radius: radius}
}
func (c *Circle) Draw() {
c.renderer.DrawCircle(c.x, c.y, c.radius)
}
type Rectangle struct {
Shape
x, y, w, h int
}
func NewRectangle(x, y, w, h int) *Rectangle {
return &Rectangle{x: x, y: y, w: w, h: h}
}
func (r *Rectangle) Draw() {
r.renderer.DrawRectangle(r.x, r.y, r.w, r.h)
}
func main() {
// 两种渲染器
ogl := &OpenGLRenderer{}
d3d := &DirectXRenderer{}
// 形状可以搭配任意渲染器
circle := NewCircle(10, 20, 5)
rect := NewRectangle(0, 0, 100, 50)
fmt.Println("用 OpenGL 渲染:")
circle.SetRenderer(ogl)
circle.Draw()
rect.SetRenderer(ogl)
rect.Draw()
fmt.Println("\n用 DirectX 渲染:")
circle.SetRenderer(d3d)
circle.Draw()
rect.SetRenderer(d3d)
rect.Draw()
}
运行结果:
用 OpenGL 渲染:
[OpenGL] 绘制圆形: 中心(10,20) 半径=5
[OpenGL] 绘制矩形: (0,0) 100x50
用 DirectX 渲染:
[DirectX] 绘制圆形: 中心(10,20) 半径=5
[DirectX] 绘制矩形: (0,0) 100x50
关键点:新增一种形状(比如三角形)不需要修改任何渲染器代码;新增一种渲染器(比如 VulkanRenderer)也不需要修改任何形状代码。两条维度独立演化。
3.2 消息发送——多渠道多格式
更贴近实际项目的场景:消息系统需要支持多种渠道(短信、邮件、推送)和多种格式(普通文本、HTML、Markdown)。
package main
import "fmt"
// MessageSender 实现接口 —— 发送渠道
type MessageSender interface {
Send(content string) error
}
// ===== 具体实现:不同渠道 =====
type SMSSender struct {
phone string
}
func (s *SMSSender) Send(content string) error {
fmt.Printf(" [短信->%s] %s\n", s.phone, content)
return nil
}
type EmailSender struct {
address string
}
func (e *EmailSender) Send(content string) error {
fmt.Printf(" [邮件->%s] %s\n", e.address, content)
return nil
}
type PushSender struct {
deviceID string
}
func (p *PushSender) Send(content string) error {
fmt.Printf(" [推送->%s] %s\n", p.deviceID, content)
return nil
}
// ===== 抽象层:消息格式 =====
type Message struct {
sender MessageSender // 桥接
}
func (m *Message) SetSender(s MessageSender) {
m.sender = s
}
// PlainTextMessage 纯文本消息
type PlainTextMessage struct {
Message
text string
}
func NewPlainTextMessage(text string) *PlainTextMessage {
return &PlainTextMessage{text: text}
}
func (m *PlainTextMessage) Send() {
m.sender.Send(m.text)
}
// HTMLMessage HTML 格式消息
type HTMLMessage struct {
Message
title string
body string
}
func NewHTMLMessage(title, body string) *HTMLMessage {
return &HTMLMessage{title: title, body: body}
}
func (m *HTMLMessage) Send() {
content := fmt.Sprintf("<html><head><title>%s</title></head><body>%s</body></html>",
m.title, m.body)
m.sender.Send(content)
}
// UrgentMessage 紧急消息(扩展抽象层)
type UrgentMessage struct {
Message
text string
}
func NewUrgentMessage(text string) *UrgentMessage {
return &UrgentMessage{text: text}
}
func (m *UrgentMessage) Send() {
content := fmt.Sprintf("【紧急】%s", m.text)
m.sender.Send(content)
}
func main() {
sms := &SMSSender{phone: "138xxxx0001"}
email := &EmailSender{address: "user@example.com"}
push := &PushSender{deviceID: "device-abc"}
// 纯文本 + 不同渠道
plain := NewPlainTextMessage("您的验证码是 123456")
fmt.Println("纯文本消息:")
plain.SetSender(sms)
plain.Send()
plain.SetSender(email)
plain.Send()
plain.SetSender(push)
plain.Send()
// HTML + 邮件
fmt.Println("\nHTML 消息:")
html := NewHTMLMessage("周报", "<h1>本周完成 3 个需求</h1>")
html.SetSender(email)
html.Send()
// 紧急 + 短信
fmt.Println("\n紧急消息:")
urgent := NewUrgentMessage("服务器宕机,请立即处理!")
urgent.SetSender(sms)
urgent.Send()
urgent.SetSender(push)
urgent.Send()
}
运行结果:
纯文本消息:
[短信->138xxxx0001] 您的验证码是 123456
[邮件->user@example.com] 您的验证码是 123456
[推送->device-abc] 您的验证码是 123456
HTML 消息:
[邮件->user@example.com] <html><head><title>周报</title></head><body><h1>本周完成 3 个需求</h1></body></html>
紧急消息:
[短信->138xxxx0001] 【紧急】服务器宕机,请立即处理!
[推送->device-abc] 【紧急】服务器宕机,请立即处理!
这个例子清晰地展示了桥接模式的价值:3 种渠道 × 3 种格式 = 9 种组合,但代码中只有 3+3=6 个类。新增渠道或格式互不影响。
3.3 数据访问层——多数据库多查询模式
package main
import "fmt"
// DBDriver 实现接口
type DBDriver interface {
Query(sql string) string
Execute(sql string) error
}
// MySQLDriver 具体实现
type MySQLDriver struct {
connStr string
}
func (d *MySQLDriver) Query(sql string) string {
return fmt.Sprintf("[MySQL:%s] 查询: %s", d.connStr, sql)
}
func (d *MySQLDriver) Execute(sql string) error {
fmt.Printf("[MySQL:%s] 执行: %s\n", d.connStr, sql)
return nil
}
// PostgresDriver 具体实现
type PostgresDriver struct {
connStr string
}
func (d *PostgresDriver) Query(sql string) string {
return fmt.Sprintf("[PG:%s] 查询: %s", d.connStr, sql)
}
func (d *PostgresDriver) Execute(sql string) error {
fmt.Printf("[PG:%s] 执行: %s\n", d.connStr, sql)
return nil
}
// Repository 抽象层
type Repository struct {
driver DBDriver
}
func (r *Repository) SetDriver(d DBDriver) {
r.driver = d
}
// UserRepository 用户仓储
type UserRepository struct {
Repository
}
func (ur *UserRepository) FindByID(id int) string {
return ur.driver.Query(fmt.Sprintf("SELECT * FROM users WHERE id=%d", id))
}
func (ur *UserRepository) Create(name string, age int) error {
return ur.driver.Execute(fmt.Sprintf("INSERT INTO users(name,age) VALUES('%s',%d)", name, age))
}
// OrderRepository 订单仓储
type OrderRepository struct {
Repository
}
func (or *OrderRepository) FindByID(id int) string {
return or.driver.Query(fmt.Sprintf("SELECT * FROM orders WHERE id=%d", id))
}
func (or *OrderRepository) Create(userID, amount int) error {
return or.driver.Execute(fmt.Sprintf("INSERT INTO orders(user_id,amount) VALUES(%d,%d)", userID, amount))
}
func main() {
mysql := &MySQLDriver{connStr: "localhost:3306"}
pg := &PostgresDriver{connStr: "localhost:5432"}
userRepo := &UserRepository{}
orderRepo := &OrderRepository{}
fmt.Println("=== MySQL 驱动 ===")
userRepo.SetDriver(mysql)
fmt.Println(userRepo.FindByID(1))
userRepo.Create("张三", 25)
orderRepo.SetDriver(mysql)
fmt.Println(orderRepo.FindByID(100))
orderRepo.Create(1, 999)
fmt.Println("\n=== PostgreSQL 驱动 ===")
userRepo.SetDriver(pg)
fmt.Println(userRepo.FindByID(2))
userRepo.Create("李四", 30)
orderRepo.SetDriver(pg)
fmt.Println(orderRepo.FindByID(200))
orderRepo.Create(2, 888)
}
运行结果:
=== MySQL 驱动 ===
[MySQL:localhost:3306] 查询: SELECT * FROM users WHERE id=1
[MySQL:localhost:3306] 执行: INSERT INTO users(name,age) VALUES('张三',25)
[MySQL:localhost:3306] 查询: SELECT * FROM orders WHERE id=100
[MySQL:localhost:3306] 执行: INSERT INTO orders(user_id,amount) VALUES(1,999)
=== PostgreSQL 驱动 ===
[PG:localhost:5432] 查询: SELECT * FROM users WHERE id=2
[PG:localhost:5432] 执行: INSERT INTO users(name,age) VALUES('李四',30)
[PG:localhost:5432] 查询: SELECT * FROM orders WHERE id=200
[PG:localhost:5432] 执行: INSERT INTO orders(user_id,amount) VALUES(2,888)
这就是 Go 标准库 database/sql 的设计思路——sql.DB 是抽象层,driver.Driver 是实现接口,不同数据库驱动实现同一个接口,上层代码完全不变。
四、桥接模式 vs 策略模式
两者结构相似——都是"持有接口引用,运行时注入",但意图不同:
| 维度 | 桥接模式 | 策略模式 |
|---|---|---|
| 解决问题 | 两个维度独立变化 | 同一维度的算法可替换 |
| 生命周期 | 实现对象通常长期持有 | 策略对象频繁切换 |
| 设计意图 | 结构型——分离抽象与实现 | 行为型——封装可互换的算法 |
| 典型场景 | 形状×渲染器、渠道×格式 | 排序算法、支付方式 |
桥接关注的是结构层面的解耦——两个维度本来就是独立的概念;策略关注的是行为层面的可替换——同一个操作有多种实现方式。
五、Go 中桥接模式的天然优势
Go 的接口是隐式实现的,这使得桥接模式在 Go 中几乎是"免费"的:
- 不需要抽象基类:Go 的结构体嵌入天然支持"持有接口引用"
- 接口小而精:
Renderer接口只定义DrawCircle和DrawRectangle,符合接口隔离 - 依赖注入自然:
SetRenderer方法就是 Go 社区常见的依赖注入方式
Go 标准库中 database/sql/driver、io.Reader/Writer、hash.Hash 等接口体系都体现了桥接思想——定义小接口,让具体实现可独立替换。
六、小结
桥接模式的核心价值是将多维度变化拆解为独立变化链:
- M×N 的类爆炸问题 → M+N 的组合方案
- 通过"持有接口引用"连接抽象层和实现层
- 新增维度不影响已有代码(开放-封闭原则)
- Go 的隐式接口让桥接模式的实现格外简洁
判断是否需要桥接模式的标准:当你发现继承体系出现两个正交的变化维度时(比如形状×渲染器、渠道×格式、数据库×仓储),就应该考虑把其中一个维度抽成接口,用组合替代继承。