本文主要介绍 Bridge 桥接模式,在 Java Design Patterns 网站上有对该模式进行介绍。这里主要是做个笔记,并添加一些扩展,以加深对该设计模式的理解。

介绍

桥接模式(Bridge Pattern)是一种结构型设计模式,用于将抽象与其实现分离,使它们可以独立地变化。桥接模式通过创建两个独立的层次结构,一个是抽象部分,一个是实现部分,来实现这种分离。

在桥接模式中,抽象部分包含抽象类或接口,定义了高层逻辑和功能。实现部分包含具体实现类,负责实现抽象部分定义的接口或方法。通过桥接模式,可以在两个层次结构中独立地扩展和变化类,而不会相互影响。同时,抽象部分和实现部分之间的耦合度降低,使系统更加灵活和可维护。

举例

考虑一下你拥有一种具有不同附魔的武器,并且应该允许将具有不同附魔的不同武器混合使用。 你会怎么做? 为每个附魔创建每种武器的多个副本,还是只是创建单独的附魔并根据需要为武器设置它? 桥接模式使您可以进行第二次操作。

翻译一下上面的武器示例。下面我们有武器的类层级:

public interface Weapon {
  void wield();
  void swing();
  void unwield();
  Enchantment getEnchantment();
}

public class Sword implements Weapon {

  private final Enchantment enchantment;

  public Sword(Enchantment enchantment) {
    this.enchantment = enchantment;
  }

  @Override
  public void wield() {
    LOGGER.info("The sword is wielded.");
    enchantment.onActivate();
  }

  @Override
  public void swing() {
    LOGGER.info("The sword is swinged.");
    enchantment.apply();
  }

  @Override
  public void unwield() {
    LOGGER.info("The sword is unwielded.");
    enchantment.onDeactivate();
  }

  @Override
  public Enchantment getEnchantment() {
    return enchantment;
  }
}

public class Hammer implements Weapon {

  private final Enchantment enchantment;

  public Hammer(Enchantment enchantment) {
    this.enchantment = enchantment;
  }

  @Override
  public void wield() {
    LOGGER.info("The hammer is wielded.");
    enchantment.onActivate();
  }

  @Override
  public void swing() {
    LOGGER.info("The hammer is swinged.");
    enchantment.apply();
  }

  @Override
  public void unwield() {
    LOGGER.info("The hammer is unwielded.");
    enchantment.onDeactivate();
  }

  @Override
  public Enchantment getEnchantment() {
    return enchantment;
  }
}

这里是单独的附魔类结构:

public interface Enchantment {
  void onActivate();
  void apply();
  void onDeactivate();
}

public class FlyingEnchantment implements Enchantment {

  @Override
  public void onActivate() {
    LOGGER.info("The item begins to glow faintly.");
  }

  @Override
  public void apply() {
    LOGGER.info("The item flies and strikes the enemies finally returning to owner's hand.");
  }

  @Override
  public void onDeactivate() {
    LOGGER.info("The item's glow fades.");
  }
}

public class SoulEatingEnchantment implements Enchantment {

  @Override
  public void onActivate() {
    LOGGER.info("The item spreads bloodlust.");
  }

  @Override
  public void apply() {
    LOGGER.info("The item eats the soul of enemies.");
  }

  @Override
  public void onDeactivate() {
    LOGGER.info("Bloodlust slowly disappears.");
  }
}

这里是两种层次结构的实践:

var enchantedSword = new Sword(new SoulEatingEnchantment());
enchantedSword.wield();
enchantedSword.swing();
enchantedSword.unwield();
// The sword is wielded.
// The item spreads bloodlust.
// The sword is swinged.
// The item eats the soul of enemies.
// The sword is unwielded.
// Bloodlust slowly disappears.

var hammer = new Hammer(new FlyingEnchantment());
hammer.wield();
hammer.swing();
hammer.unwield();
// The hammer is wielded.
// The item begins to glow faintly.
// The hammer is swinged.
// The item flies and strikes the enemies finally returning to owner's hand.
// The hammer is unwielded.
// The item's glow fades.

类图

alt text

适用场景

桥接模式适用于以下情况:

  1. 当你希望在抽象部分实现部分之间存在独立的扩展和变化时,可以使用桥接模式。这样可以避免在两个层次结构之间的紧耦合关系,使它们可以相互独立地进行修改和扩展。
  2. 当你想要避免在编译时将抽象部分与实现部分绑定在一起时,桥接模式是一个很好的选择。通过桥接模式,可以在运行时动态地将抽象部分和实现部分进行组合,而不需要修改客户端的代码。
  3. 当你有多个独立变化的维度时,可以使用桥接模式。例如,在给定的示例中,武器和附魔是两个独立变化的维度,通过桥接模式可以灵活地组合它们,而不需要为每种武器和每种附魔创建大量的子类。

总的来说,桥接模式适用于需要在抽象与实现之间存在独立变化和组合的情况,以及需要动态地进行选择和共享实现的情况。它能够提高系统的灵活性、可扩展性和可维护性。

举例

以下是一个实际的例子,以帮助说明桥接模式的应用。

假设你正在开发一个绘图应用程序,其中包含不同类型的形状(如圆形、矩形)和不同的颜色(如红色、蓝色)。你希望能够在任意形状上应用不同的颜色,而不是为每种形状和颜色的组合创建大量的子类。

在这种情况下,可以使用桥接模式来实现形状和颜色之间的组合。首先,定义一个抽象的形状类 Shape,其中包含一个颜色对象的引用。然后,定义一个抽象的颜色类 Color,其中包含一个绘制方法。通过桥接模式,将形状和颜色进行分离,使它们可以独立地进行扩展和变化。

下面是示例代码:

// 形状抽象类
abstract class Shape {
    protected Color color;

    public Shape(Color color) {
        this.color = color;
    }

    public abstract void draw();
}

// 颜色抽象类
interface Color {
    void applyColor();
}

// 具体形状类:圆形
class Circle extends Shape {
    public Circle(Color color) {
        super(color);
    }

    @Override
    public void draw() {
        System.out.print("绘制圆形,");
        color.applyColor();
    }
}

// 具体形状类:矩形
class Rectangle extends Shape {
    public Rectangle(Color color) {
        super(color);
    }

    @Override
    public void draw() {
        System.out.print("绘制矩形,");
        color.applyColor();
    }
}

// 具体颜色类:红色
class RedColor implements Color {
    @Override
    public void applyColor() {
        System.out.println("使用红色");
    }
}

// 具体颜色类:蓝色
class BlueColor implements Color {
    @Override
    public void applyColor() {
        System.out.println("使用蓝色");
    }
}

现在,你可以创建不同的形状对象,并将不同的颜色对象与之组合,而无需创建大量的子类。例如:

Shape redCircle = new Circle(new RedColor());
Shape blueRectangle = new Rectangle(new BlueColor());

redCircle.draw(); // 输出:绘制圆形,使用红色
blueRectangle.draw(); // 输出:绘制矩形,使用蓝色

通过桥接模式,你可以轻松地扩展形状和颜色的种类,而不需要修改现有的代码。你可以添加新的形状或颜色类,并将它们组合在一起,以满足不同的需求,同时保持了形状和颜色之间的独立性。

开源框架中的应用

在开源框架中,有一些使用桥接模式的例子。以下是其中一些常见的开源框架和其使用桥接模式的示例:

  1. JDBC(Java数据库连接)框架:JDBC框架是Java中用于与数据库进行交互的标准API。在JDBC中,使用了桥接模式来连接不同的数据库驱动程序和应用程序。桥接模式将JDBC API与特定的数据库驱动程序实现分离,使得应用程序可以独立于不同的数据库实现进行开发。
  2. AWT(Abstract Window Toolkit):AWT是Java中用于创建图形用户界面(GUI)的原始工具包。在AWT中,使用了桥接模式来分离抽象的GUI组件(例如按钮、文本框)和具体的平台实现。这样可以使得AWT可以在不同的操作系统上运行,并且可以根据不同的平台提供不同的外观和行为。
  3. Spring框架:Spring是一个开源的Java企业应用程序开发框架。在Spring中,使用了桥接模式来实现不同层之间的解耦。例如,Spring的数据访问模块(Spring Data)使用了桥接模式来连接不同的数据访问技术,如JPA、Hibernate、MyBatis等。这样,开发人员可以选择适合其需求的数据访问技术,而不需要修改其他部分的代码。
  4. Apache Log4j:Log4j框架使用了桥接模式将日志记录器(Logger)与具体的日志输出(Appender)分离。通过Logger接口和Appender接口的组合使用,可以在运行时动态地将日志记录器与不同的日志输出实现进行桥接。例如,可以将Logger桥接到FileAppenderConsoleAppenderDatabaseAppender等不同的具体实现上。
  5. Apache Commons IO:Commons IO是Apache Commons项目的一部分,提供了一组用于处理I/O操作的实用工具。在Commons IO中,使用了桥接模式来分离抽象的I/O操作(如文件读写、流处理)和具体的实现。这使得开发人员可以在不同的环境中使用相同的API进行I/O操作。
  6. Hibernate ORM:Hibernate使用了桥接模式将不同数据库厂商的驱动程序与核心功能分离。它通过DriverManager接口和具体的数据库驱动程序实现的桥接,能够在运行时动态地选择和切换数据库驱动程序。这样,开发人员可以使用相同的Hibernate API与不同的数据库进行交互,而不需要修改核心代码。
  7. Retrofit:Retrofit库使用了桥接模式来将网络请求的抽象表示与具体的HTTP客户端实现分离。它通过Call接口和HttpClient接口的组合使用,可以将不同的HTTP客户端库(如OkHttp、Apache HttpClient)桥接到统一的网络请求抽象上。这使得开发人员可以根据需要选择不同的HTTP客户端实现,而不需要修改使用Retrofit的代码。
  8. Apache HttpClient:HttpClient是Apache软件基金会提供的一个用于发送HTTP请求的Java库。它使用了桥接模式来将抽象的HTTP请求和具体的HTTP协议实现(如HTTP/1.1或HTTP/2)分离。这使得开发人员可以根据需要选择不同的HTTP协议版本,而不需要修改代码。
  9. Apache Commons Logging:Commons Logging是Apache Commons项目中的一个通用日志记录接口。它使用了桥接模式来将应用程序代码与底层的具体日志实现(如Log4j、java.util.logging等)分离。这使得开发人员可以在不同的环境中切换和配置不同的日志实现。

以下是一个简单的示例代码,演示了桥接模式在JDBC框架中的应用:

首先,定义桥接接口 DatabaseDriver,它包含了数据库操作的方法声明:

public interface DatabaseDriver {
    void connect(String url, String username, String password);
    void executeQuery(String query);
    void disconnect();
}

然后,实现具体的数据库驱动程序,例如 MySQLDriverOracleDriver,它们实现了 DatabaseDriver 接口:

public class MySQLDriver implements DatabaseDriver {
    // MySQL数据库特定的实现代码

    @Override
    public void connect(String url, String username, String password) {
        // 连接MySQL数据库的代码
    }

    @Override
    public void executeQuery(String query) {
        // 执行MySQL查询的代码
    }

    @Override
    public void disconnect() {
        // 断开MySQL数据库连接的代码
    }
}

public class OracleDriver implements DatabaseDriver {
    // Oracle数据库特定的实现代码

    @Override
    public void connect(String url, String username, String password) {
        // 连接Oracle数据库的代码
    }

    @Override
    public void executeQuery(String query) {
        // 执行Oracle查询的代码
    }

    @Override
    public void disconnect() {
        // 断开Oracle数据库连接的代码
    }
}

接下来,定义使用桥接模式的应用程序代码,其中包含了一个 Database 类作为抽象化角色,它使用桥接接口 DatabaseDriver 进行数据库操作:

public class Database {
    private DatabaseDriver driver;

    public Database(DatabaseDriver driver) {
        this.driver = driver;
    }

    public void connectToDatabase(String url, String username, String password) {
        driver.connect(url, username, password);
    }

    public void executeQuery(String query) {
        driver.executeQuery(query);
    }

    public void disconnectFromDatabase() {
        driver.disconnect();
    }
}

最后,可以在应用程序中使用这些类进行数据库操作:

public class Main {
    public static void main(String[] args) {
        // 创建MySQLDriver实例
        DatabaseDriver mysqlDriver = new MySQLDriver();

        // 创建Database实例,并使用MySQLDriver进行操作
        Database mysqlDatabase = new Database(mysqlDriver);

        // 连接到MySQL数据库
        mysqlDatabase.connectToDatabase("jdbc:mysql://localhost:3306/mydb", "username", "password");

        // 执行MySQL查询
        mysqlDatabase.executeQuery("SELECT * FROM mytable");

        // 断开MySQL数据库连接
        mysqlDatabase.disconnectFromDatabase();

        // 创建OracleDriver实例
        DatabaseDriver oracleDriver = new OracleDriver();

        // 创建Database实例,并使用OracleDriver进行操作
        Database oracleDatabase = new Database(oracleDriver);

        // 连接到Oracle数据库
        oracleDatabase.connectToDatabase("jdbc:oracle:thin:@localhost:1521:xe", "username", "password");

        // 执行Oracle查询
        oracleDatabase.executeQuery("SELECT * FROM mytable");

        // 断开Oracle数据库连接
        oracleDatabase.disconnectFromDatabase();
    }
}

这个例子展示了如何使用桥接模式将JDBC API与特定的数据库驱动程序实现分离。通过创建不同的数据库驱动程序实例,并将其传递给 Database 类,应用程序可以独立于不同的数据库实现进行开发和操作。

参考文章