文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

深入浅出设计模式【七、桥接模式】

一、桥接模式介绍

桥接模式处理的是一个类存在多个维度的变化。如果使用继承,会导致类的数量急剧增长(M*N个类),且扩展困难(增加一个维度或一个变化都需要修改很多代码)。

该模式建议将其中一个维度抽取出来,变成独立的层次结构,并在原始类中引用这个新层次的对象。这样,原始类中的所有操作都可以委托给这个引用的对象来完成。这种分离使得两个层次可以独立扩展,而不会相互影响。

二、核心概念与意图

  1. 核心概念

    • 抽象 (Abstraction): 定义抽象接口,其中包含一个对实现对象的引用。它通常定义了一些基于底层实现的高层控制逻辑。
    • 精化抽象 (Refined Abstraction): 是抽象的子类,扩展或改变了抽象定义的接口。
    • 实现者 (Implementor): 定义实现类的接口。这个接口不一定要与抽象的接口完全一致;实际上,这两个接口可以完全不同。一般来讲,实现者接口只提供基本操作。
    • 具体实现 (Concrete Implementor): 实现实现者接口,提供具体的操作实现。
  2. 意图

    • 将抽象部分与它的实现部分分离,使它们都可以独立地变化
    • 使用“组合/聚合”关系代替继承关系,从而获得比继承更好的灵活性。
    • 避免在多维度变化时,使用继承导致的类层次结构过于复杂(“类爆炸”)的问题

三、适用场景剖析

桥接模式在以下场景中非常有效:

  1. 一个类存在多个独立变化的维度,且这些维度都需要进行扩展时: 例如,一个图形类,其形状(圆形、方形)和颜色(红色、蓝色)都是可以独立变化的维度。使用桥接模式,形状是抽象,颜色是实现。
  2. 需要在抽象和实现之间提供更多的灵活性时: 例如,希望在运行时动态地切换实现(如更换数据库驱动)。
  3. 不希望使用继承,或因为多层继承导致类的数量急剧增加时: 继承是一种强耦合的静态关系,桥接模式通过组合提供了更松散的耦合。
  4. 需要对客户端隐藏实现细节时: 客户端只依赖于抽象接口,完全不知道实现的细节,这符合“依赖倒置原则”。

四、UML 类图解析

以下Mermaid类图清晰地展示了桥接模式的结构和角色间的关系,它完美体现了两个独立变化的层次:

Client
Abstraction
-implementor: Implementor
+operation()
RefinedAbstraction
+operation()
«interface»
Implementor
+operationImpl()
ConcreteImplementorA
+operationImpl()
ConcreteImplementorB
+operationImpl()
  • Abstraction抽象部分的基类。它持有一个对 Implementor 对象的引用(桥接的核心)。它的 operation() 方法通常会调用 implementor.operationImpl()
  • RefinedAbstraction抽象部分的扩展,可以增加或修改抽象部分的行为。
  • Implementor实现部分的接口。它定义了底层、基础的操作。注意,它的接口可能和 Abstraction 的接口完全不同。
  • ConcreteImplementorA, ConcreteImplementorB实现部分的具体实现,真正实现 Implementor 接口定义的方法。
  • Client: 只需要与 Abstraction 层次结构交互,完全不知道 Implementor 层次结构的存在。

关键AbstractionImplementor 之间是组合关系,这条线就是“桥”。通过这座桥,抽象和实现可以独立发展。

五、各种实现方式及其优缺点

桥接模式的实现相对标准,但其设计思想比实现方式更重要。

1. 标准实现(基于接口/抽象类)

即上述UML所描述的方式,这是最经典和推荐的方式。

// 实现部分接口
public interface DrawingAPI { // Implementor
    void drawCircle(double x, double y, double radius);
}

// 具体实现A
public class WindowsAPI implements DrawingAPI {
    @Override
    public void drawCircle(double x, double y, double radius) {
        System.out.printf("WindowsAPI.circle at (%f, %f) radius %f\n", x, y, radius);
    }
}

// 具体实现B
public class LinuxAPI implements DrawingAPI {
    @Override
    public void drawCircle(double x, double y, double radius) {
        System.out.printf("LinuxAPI.circle at (%f, %f) radius %f\n", x, y, radius);
    }
}

// 抽象部分
public abstract class Shape { // Abstraction
    protected DrawingAPI drawingAPI; // 桥接的关键引用

    protected Shape(DrawingAPI drawingAPI) {
        this.drawingAPI = drawingAPI;
    }
    public abstract void draw();
}

// 精化抽象
public class CircleShape extends Shape { // RefinedAbstraction
    private double x, y, radius;
    public CircleShape(double x, double y, double radius, DrawingAPI drawingAPI) {
        super(drawingAPI);
        this.x = x;
        this.y = y;
        this.radius = radius;
    }
    @Override
    public void draw() {
        // 抽象部分调用实现部分
        drawingAPI.drawCircle(x, y, radius);
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {
        Shape circle1 = new CircleShape(1, 2, 3, new WindowsAPI());
        Shape circle2 = new CircleShape(5, 7, 11, new LinuxAPI());

        circle1.draw(); // 输出: WindowsAPI.circle at (1.000000, 2.000000) radius 3.000000
        circle2.draw(); // 输出: LinuxAPI.circle at (5.000000, 7.000000) radius 11.000000
    }
}
  • 优点
    • 符合开闭原则: 可以独立地扩展抽象层次和实现层次,无需修改现有代码。新增一种形状或一种绘图API都非常容易。
    • 符合单一职责原则: 抽象部分专注于高层逻辑,实现部分专注于底层细节。
    • 极大的灵活性: 可以在运行时动态地改变实现(通过setter方法修改 drawingAPI 引用)。
  • 缺点
    • 增加了系统的复杂性: 对高内聚的类使用桥接模式可能会使代码变得更加复杂,显得“过度设计”。

六、最佳实践

  1. 识别变化维度: 成功应用桥接模式的关键在于识别出系统中哪些是独立变化的维度。一个维度是抽象,另一个(或多个)维度是实现。
  2. 组合优于继承: 桥接模式是“组合优于继承”这一原则的杰出体现。当你发现使用继承来处理多个变化维度会导致类层次结构复杂时,就应考虑使用桥接模式。
  3. 不要过度设计: 如果系统只有一个变化的维度,或者多个维度但变化非常稳定,那么简单的继承可能就足够了。桥接模式是针对特定复杂场景的解决方案。
  4. 与策略模式区分
    • 桥接模式: 关注于长期存在的、结构性的“抽象-实现”分离。它们的关系是静态的(虽然在运行时可以切换)。例如,一个形状和它的绘制方式。
    • 策略模式: 关注于短期的、行为性的算法替换。策略是完成特定任务的一种可选方式,通常更轻量级,切换更频繁。例如,排序算法。

七、在开发中的演变和应用

桥接模式的思想在现代架构和框架设计中是基础性的:

  1. 驱动程序设计: 桥接模式是驱动程序架构的理论基础。操作系统或框架定义抽象的接口(如JDBC的 Connection, Statement),而数据库厂商提供具体的实现(如MySQL Driver, Oracle Driver)。应用程序只依赖于抽象接口,可以随时切换底层驱动(实现)。
  2. 插件架构: 许多软件支持插件功能。核心程序是“抽象”,定义了插件接口(“实现者接口”),而第三方插件是“具体实现”。这允许程序的功能被独立地扩展。
  3. 跨平台开发: 抽象部分定义业务逻辑,而实现部分提供针对不同平台(Windows, macOS, Linux)的具体实现。这使得核心业务逻辑可以复用,而只需为每个平台编写少量的原生代码。

八、真实开发案例(Java语言内部、知名开源框架、工具)

  1. JDBC (Java Database Connectivity)
    这是桥接模式最经典的应用。

    • Abstractionjavax.sql.DataSource, java.sql.Connection, java.sql.Statement, java.sql.ResultSet。这些是Java标准库定义的抽象接口。
    • Implementor: 各大数据库厂商提供的驱动包,如 mysql-connector-java.jar。它包含了 ConnectionImpl, StatementImpl 等具体实现类。
    • 桥接: 应用程序代码只依赖于JDBC抽象接口。通过 Class.forName("com.mysql.cj.jdbc.Driver")DriverManager.getConnection(),程序在运行时将抽象的 Connection 桥接到MySQL的具体实现上。更换数据库只需更换驱动JAR包和连接字符串,无需修改业务代码
  2. SLF4J (Simple Logging Facade for Java)
    SLF4J是一个日志门面(抽象),它背后可以桥接到多种日志实现(实现)。

    • Abstractionorg.slf4j.Logger, org.slf4j.LoggerFactory
    • Implementor: Logback, Log4j 2, java.util.logging 等。
    • 桥接: 应用程序使用SLF4J的API打印日志。通过在项目中引入不同的绑定包(如 slf4j-log4j12.jar),SLF4J会在运行时自动桥接到对应的日志实现上。
  3. AWT/Swing中的Peer架构
    Java的AWT GUI工具包早期使用了桥接模式(Peer架构)。

    • Abstractionjava.awt.Button, java.awt.Window 等。
    • Implementor: 平台相关的对等实体(Peer),如 sun.awt.windows.WButtonPeer
    • 抽象AWT组件将所有的操作委托给一个平台相关的实现(Peer),从而实现了“编写一次,到处运行”。

九、总结

方面总结
模式类型结构型设计模式
核心意图将抽象部分与它的实现部分分离,使它们都可以独立地变化。
关键角色抽象 (Abstraction)、精化抽象 (RefinedAbstraction)、实现者 (Implementor)、具体实现 (ConcreteImplementor)
主要优点1. 解耦抽象与实现:极大提高了系统的灵活性。
2. 取代多层继承:解决了类爆炸问题,层次清晰。
3. 符合开闭原则和单一职责原则:抽象和实现可以独立扩展。
主要缺点1. 增加理解与设计难度:需要能正确识别系统中两个独立变化的维度。
2. 可能增加代码复杂度:对简单系统可能显得过度设计。
适用场景1. 一个类存在多个独立变化的维度,且都需要扩展。
2. 不希望使用继承导致类爆炸。
3. 需要在运行时动态切换实现。
核心手段使用组合/聚合关系代替继承关系
关系与对比vs. 适配器模式: 适配器是事后补救,用于连接两个不兼容的接口。桥接模式是事前设计,用于将抽象和实现分离,使它们能独立演化。
现代应用JDBC日志门面(SLF4J) 等标准规范的核心设计思想,是构建可扩展框架和组件的基础。

桥接模式是一种理解起来略有难度,但一旦掌握就会极大提升架构设计能力的高级模式。它教导我们不要急于使用继承来搭建关系,而是先分析变化的维度,并通过组合来构建更灵活、更健壮的系统。它是构建大型、可扩展框架(如Spring、JDBC)的理论基石,是区分普通程序员和架构师的重要知识标志。

posted @ 2025-08-29 13:07  NeoLshu  阅读(3)  评论(0)    收藏  举报  来源