配套代码:GitHub 仓库 · 本篇完整源码 · 行为测试。使用 JDK 25 与 Gradle,包名为 com.hanserwei.patterns.bridge

系列导航:Java 25 设计模式学习指南

上一篇:适配器 · 下一篇:组合

通知有普通提示与警报两种业务类型,也有纯文本与 Markdown 两种呈现方式。如果用继承枚举组合,会出现 TextInfoNotice、MarkdownInfoNotice、TextAlertNotice、MarkdownAlertNotice;新增一种呈现方式后,还要再增加一整排组合类。

问题不在类名太长,而在两条独立变化的轴被绑进同一棵继承树。桥接让业务类型保留自己的层级,让呈现实现通过组合接入。

从问题提炼设计意图

桥接把抽象部分与实现部分分离,使两者可以独立扩展。这里的抽象部分是通知业务,实现部分是呈现策略接口及其实现。

对象职责与协作关系

示例角色 职责
Notice 业务抽象,拥有 display 流程并持有 Renderer
InfoNotice、AlertNotice 扩充抽象,定义不同业务标题
Renderer 实现接口,约定如何呈现标题和正文
TextRenderer、MarkdownRenderer 具体呈现实现
classDiagram
    Notice <|-- InfoNotice
    Notice <|-- AlertNotice
    Notice --> Renderer : bridge
    Renderer <|.. TextRenderer
    Renderer <|.. MarkdownRenderer

代码思路:变化应该落在哪个对象上

Notice 不继承任何 Renderer,而是保存一个 private final Renderer 字段。构造器决定把哪种实现与本次业务对象配对,display 只调用接口,不检查 renderer 的具体类型。

业务标题由 title 抽象方法提供,所以 AlertNotice 只关心 ALERT 这层业务意义。MarkdownRenderer 只关心把给定标题和正文排成 Markdown,它无需知道输入来自哪种通知。

这样,新增通知类型只扩展 Notice 一侧,新增呈现器只扩展 Renderer 一侧。两种业务乘两种实现仍有四种运行组合,但不需要四个组合子类。测试覆盖这四种搭配,证明两条轴确实可以自由组合。

关键实现与独立运行

配套仓库中的包名是 com.hanserwei.patterns.bridge,源码目录为 src/main/java/com/hanserwei/patterns/bridge/。仓库地址统一见系列导航。以下展示关键文件的完整内容;其余角色和测试在同一仓库中,每个顶级类型各占一个文件。

Notice.java

package com.hanserwei.patterns.bridge;

import java.util.Objects;

/** 通知业务抽象,通过组合连接可独立变化的呈现实现. */
public abstract class Notice {
  /** 本通知使用的呈现器. */
  private final Renderer renderer;

  /** 装配呈现实现. */
  protected Notice(Renderer renderer) {
    this.renderer = Objects.requireNonNull(renderer, "renderer");
  }

  /** 组合业务标题与非空正文,交由呈现器输出. */
  public final String display(String body) {
    return renderer.render(title(), Objects.requireNonNull(body, "body"));
  }

  /** 由具体通知定义业务标题. */
  protected abstract String title();
}

AlertNotice.java

package com.hanserwei.patterns.bridge;

/** 通知业务维度中的具体类型. */
public final class AlertNotice extends Notice {
  /** 接收独立选择的呈现器. */
  public AlertNotice(Renderer renderer) {
    super(renderer);
  }

  /** 返回本通知类型的业务标题. */
  @Override
  protected String title() {
    return "ALERT";
  }
}

MarkdownRenderer.java

package com.hanserwei.patterns.bridge;

/** 桥接结构中的具体呈现实现. */
public final class MarkdownRenderer implements Renderer {
  /** 呈现已经由通知对象准备好的标题和正文. */
  @Override
  public String render(String title, String body) {
    return "## " + title + "\n" + body;
  }
}

Demo.java 展示调用方如何装配这些对象:

package com.hanserwei.patterns.bridge;

/** 演示本章对象的装配方式和可观察结果. */
public final class Demo {
  /** 禁止实例化演示入口. */
  private Demo() {}

  /** 运行独立示例;args 为未使用的命令行参数. */
  public static void main(String[] args) {
    Notice notice = new AlertNotice(new MarkdownRenderer());
    System.out.println(notice.display("Review required"));
  }
}

在配套代码仓库根目录运行;Windows 使用 gradlew.bat 替换 ./gradlew

./gradlew runBridge
./gradlew test --tests 'com.hanserwei.patterns.bridge.PatternTest'

示例的业务输出如下,省略 Gradle 自身的任务提示:

## ALERT
Review required

用测试确认模式的行为

四种组合分别得到正确标题与格式;缺失呈现器时拒绝构造。组合覆盖可以发现某个业务子类偷偷绑定具体呈现器的问题。

对应测试位于 src/test/java/com/hanserwei/patterns/bridge/PatternTest.java。建议先运行现有测试,再改动一个协作环节,观察哪个断言能够发现问题。

常见用法

  • 消息种类与发送或呈现实现需要分别扩展。
  • 业务报表与输出设备、文件格式存在独立组合。
  • 跨平台图形组件的高层行为和底层绘制实现需要解耦。

适用边界与容易踩的坑

不要因为存在一个接口字段就立刻称为桥接。需要先识别两条确实有独立变化需求的轴;如果只替换一次算法,策略模式的描述可能更准确。

桥接接口应稳定且足够小。若每增加一种通知都必须给 Renderer 添加专用方法,业务细节仍然穿透到了实现层,两个维度就没有真正分开。反过来,也不能为了统一接口而丢失关键能力。

本例保留装配时固定的 Renderer,没有提供任意 setRenderer。运行期间能否更换实现是业务选择,不是模式硬性要求;固定依赖更容易推理生命周期。Markdown 呈现仅供教学,不包含完整语法转义。

与相近模式比较

桥接强调抽象层级和实现层级独立变化;策略强调给上下文替换某个算法。类图可能接近,判断时应该看变化需求和职责,而不是只看箭头。

动手练习

新增 SuccessNotice 与 JsonRenderer,要求两个改动互不修改对方已有类型。为新增组合写测试,然后检查 Renderer 是否被迫识别通知子类;如果出现 instanceof,重新检查职责边界。

系列导航:Java 25 设计模式学习指南

上一篇:适配器 · 下一篇:组合