讲解

毛笔字好不好看,取决于两支「笔」:拿笔的手(写字的技法:楷书、行书)和笔本身(羊毫、狼毫)。技法和笔是两个独立变化的维度——3 种技法 × 2 种笔 = 6 种组合。如果给每种组合都做一个类,维度一多就是类的笛卡尔积爆炸(3 × 5 × 4 = 60 个类)。桥接模式的解法:让两个维度各自继承演化,中间用一座「桥」(组合关系)连起来,运行时自由配对。

GoF 的定义比较绕:将抽象部分与它的实现部分分离,使它们都可以独立地变化。落到代码里就是:抽象(比如「消息」:普通消息、紧急消息)持有一个实现接口的引用(比如「发送渠道」:邮件、短信、推送),抽象的方法委托给实现接口完成。两个类层级各自生长,组合数变成加法(3 + 5 个类)。

桥接和适配器经常被一起问:结构上都是「一个类持有另一个接口的引用」。区别在于意图和时机——适配器是事后补救,把已有的不兼容接口接起来;桥接是事前设计,主动把两个预期都会变化的维度拆开。看动机不看类图,是区分很多「长得像」的模式的关键。

适用场景:一个类存在两个(或多个)独立变化的维度;想在运行时切换实现;抽象和实现都需要被子类化扩展。JDBC 是经典案例:你的代码面向 Driver 抽象,数据库厂商提供各自实现,两边独立演化。前端里「组件主题 × 组件类型」也是同一个结构。

示例

消息类型(普通/紧急)× 发送渠道(邮件/短信),两个维度自由组合,总共只需要 2 + 2 个类而不是 4 个组合类:

import assert from 'node:assert/strict';

// 实现侧:发送渠道(可以独立扩展出推送、Webhook……)
interface MessageSender {
  send(text: string): string;
}

class EmailSender implements MessageSender {
  send(text: string): string {
    return '[邮件] ' + text;
  }
}

class SmsSender implements MessageSender {
  send(text: string): string {
    return '[短信] ' + text;
  }
}

// 抽象侧:消息类型,持有渠道引用 —— 这就是「桥」
abstract class Message {
  constructor(protected readonly sender: MessageSender) {}
  abstract deliver(): string;
}

class NormalMessage extends Message {
  constructor(sender: MessageSender, private readonly text: string) {
    super(sender);
  }
  deliver(): string {
    return this.sender.send(this.text);
  }
}

class UrgentMessage extends Message {
  constructor(sender: MessageSender, private readonly text: string) {
    super(sender);
  }
  deliver(): string {
    return this.sender.send('【紧急】' + this.text);
  }
}

// 运行时自由配对:类型和渠道各自独立演化
const messages: Message[] = [
  new NormalMessage(new EmailSender(), '周报已更新'),
  new UrgentMessage(new SmsSender(), '服务器宕机'),
  new UrgentMessage(new EmailSender(), '服务器宕机'),
];
const results = messages.map((m) => m.deliver());
assert.deepEqual(results, ['[邮件] 周报已更新', '[短信] 【紧急】服务器宕机', '[邮件] 【紧急】服务器宕机']);
console.log(results.join('\n'));

看扩展性:新增「推送渠道」只需加一个 PushSender 类,三种消息类型立刻都能用它;新增「定时消息」类型只需加一个子类,三种渠道立刻都能承载它。两个维度各加各的,类数量线性增长而不是乘积增长——这就是「独立变化」四个字的含金量。

常见坑

  • 只有一个实现也搭桥:桥接的收益来自「两个维度都会变」;实现侧永远只有一个类时,桥是纯粹的间接层,直接继承更简单。
  • 把桥接做成适配器:桥接两侧都是你设计的新抽象;如果一侧是改不了的遗留类,那是适配器的活儿(或者适配器 + 桥接组合使用)。
  • 抽象层泄漏实现细节:Message 的接口里出现 email 字样,说明维度没拆干净——抽象的 API 必须对实现方式一无所知。
  • 桥接后忘了注入点:渠道的选取应该集中在入口(读配置决定),散落的 new EmailSender() 会让「运行时切换」名存实亡。

小结

桥接把「抽象」和「实现」两个变化轴用组合连起来,类数量从乘积变加法,运行时自由配对。它和适配器形似而神异:桥接是事前分轴,适配器是事后对接。下一章看处理树形结构的组合模式。