Skip to content
难度基础(★)
建议时长45分钟

12.5.4 例题4:桥接模式

本节例题是绘图软件:系统要用不同绘图程序绘制不同图形,例如直线和圆形。图形类型会扩展,绘图程序也会扩展。课程借这个例题讲桥接模式,并特别提醒:题干中的表格、方法名和参数顺序都可能直接决定填空答案。

桥接模式要解决“两个维度都变化”

如果只有一个变化维度,继承还能处理。例如只有图形变化,可以有 LineCircle;只有绘图程序变化,可以有 DP1DP2。但本题同时有两个维度:

维度例题含义可能变化
抽象维度图形:直线、圆形、矩形等新增图形
实现维度绘图程序:DP1、DP2 等新增绘图平台/程序

如果用继承硬拼,会出现 DP1LineDP2LineDP1CircleDP2Circle……每新增一个图形或一个绘图程序,类数量交叉增长,这就是类爆炸。

mermaid
flowchart TD
  Bad["继承硬拼"] --> B1["DP1Line"]
  Bad --> B2["DP2Line"]
  Bad --> B3["DP1Circle"]
  Bad --> B4["DP2Circle"]
  Good["桥接拆分"] --> Shape["Shape 图形层"]
  Good --> Drawing["Drawing 绘图程序层"]
  Shape --> Bridge["Shape 持有 Drawing"]
  Drawing --> Bridge

桥接结构

桥接模式把两个维度拆成两个继承体系,再用组合把它们连起来。

角色在例题中的含义填空线索
Implementor绘图程序接口,如 Drawingimplements Drawing 反推它是接口
ConcreteImplementorV1DrawingV2Drawing实现画线、画圆等底层方法
AbstractionShape 抽象图形持有 Drawing 成员
RefinedAbstractionLineCircle调用 Drawing 完成绘制

接口空:从实现类反推

课程中第一组空出现在类似 Drawing 的接口上。判断依据是:下方类写了 implements Drawing,说明 Drawing 是接口;实现类中出现了画线、画圆两个方法,说明接口中缺的就是这两个方法声明。

java
interface Drawing {
    void drawLine(double x1, double y1, double x2, double y2);
    void drawCircle(double x, double y, double r);
}

接口方法声明有几个关键点:

要素是否能省原因
返回值类型不能方法签名必需
方法名不能要与实现类一致
参数列表不能参数个数和顺序会影响调用
方法体 {}不能写接口方法不写具体实现
abstract通常不用写接口方法天然抽象

表格不是装饰:它决定方法名

课程提醒,题干中如果在常规描述之外给出表格,后面通常会用到。本题表格列出不同绘图程序的实际方法名,例如:

绘图程序画线方法画圆方法
DP1drawLine(...)draw_a_circle(x, y, r)
DP2drawline(...) 或其他命名drawCircle(x, y, r)

不同程序的方法名可能相近但不完全相同。填空时要先确认当前类调用的是 DP1 还是 DP2,再去表格中找对应方法,不能把 DP2 的方法名填到 DP1 的实现里。

实现类方法体空:先定位当前程序

V1Drawing 中,如果它封装的是 DP1,那么画圆就应该调用 DP1 的画圆方法:

java
class V1Drawing implements Drawing {
    private DP1 dp1 = new DP1();

    public void drawCircle(double x, double y, double r) {
        dp1.draw_a_circle(x, y, r);
    }
}

V2Drawing 中,如果它封装的是 DP2,就要调用 DP2 对应方法:

java
class V2Drawing implements Drawing {
    private DP2 dp2 = new DP2();

    public void drawCircle(double x, double y, double r) {
        dp2.drawCircle(x, y, r);
    }
}

这类空的推导顺序是:

  1. 当前类是 V1Drawing 还是 V2Drawing
  2. 当前类内部持有的是 DP1 还是 DP2。
  3. 表格中该程序画线/画圆的方法名是什么。
  4. 参数名和参数顺序是否与表格一致。

抽象类空:看到 abstract class 要警惕

课程后半段讲 Shape 抽象类。它持有绘图程序对象,并有构造函数、具体的 drawLine 或辅助方法。若它被声明为 abstract class,但代码中看不到抽象方法,就要怀疑空缺处是抽象方法。

java
abstract class Shape {
    protected Drawing drawing;

    public Shape(Drawing drawing) {
        this.drawing = drawing;
    }

    public abstract void draw();
}

class Circle extends Shape {
    public Circle(Drawing drawing) {
        super(drawing);
    }

    public void draw() {
        drawing.drawCircle(x, y, r);
    }
}

在抽象类中声明抽象方法时,abstract 关键字不能漏,方法体也不能写。

参数顺序是隐形扣分点

课程特别提到,类似题中曾考过参数顺序:一个方法可能是 x1, y1, x2, y2,另一个方法可能是 x1, x2, y1, y2。这不是小问题,顺序错了就等于调用语义错。

看到的线索正确做法
表格给了方法签名严格按表格抄参数顺序
代码变量名相似不凭习惯重排
方法名来自不同程序重新核对对应程序的签名

桥接模式的优劣

维度优点代价
扩展性图形和绘图程序可独立扩展初始结构比直接继承复杂
类数量避免交叉继承导致类爆炸需要多理解一层委托关系
复用性Shape 复用 Drawing 接口,具体程序可替换调试时调用链更长

桥接模式之所以替代“多层继承硬拼”,不是因为继承过时,而是因为当变化维度超过一个时,继承会把维度耦合在同一个类名里,扩展成本呈乘法增长。桥接把乘法拆成加法。

与适配器的辨析

对比项适配器桥接
主要目的接口转换两个维度独立变化
典型题干已有类不能满足新接口新图形、新平台/程序都可能扩展
代码重点Adapter 调用 AdapteeAbstraction 持有 Implementor
设计时机常是后期补救常是前期设计

例题

单选
桥接模式最典型的适用场景是:

自查清单

  1. 能否说明为什么“图形 × 绘图程序”会导致类爆炸?
  2. 能否从 implements 反推出接口及其方法声明?
  3. 能否根据表格找到 DP1/DP2 正确方法名?
  4. 能否严格保留参数列表和参数顺序?
  5. 能否区分桥接模式与适配器模式的动机?