返回 导航

其他

hangge.com

Java - GoF设计模式详解7(桥接模式)

作者:hangge | 2023-03-22 08:40

七、桥接模式

1,基本介绍

(1)桥接模式(Bridge)把类的抽象部分同实现部分分离开来,这样类的抽象和实现都可以独立地变化(从而实现接口与实现分离)。
(2)桥接模式分为抽象部分和实现部分。通常,抽象部分是一个接口或抽象类,而实现部分是一个实现了该接口或抽象类的具体类。桥接模式使用组合关系(而不是继承关系)来结合抽象部分和实现部分。
(3)该模式中包含的角色及其职责如下:
  • 抽象化Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
  • 扩展抽象化Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  • 实现化Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
  • 具体实现化Concrete Implementor)角色:给出实现化角色接口的具体实现。

2,使用样例

(1)假设我们需要进行付款处理(包括线上付款和线下付款),而付款方式有可以选择银行卡支付、微信支付。这种情况就可以使用桥接模式。首先定义一个实现化角色,即 Payment 支付接口:
// 支付接口
public interface Payment {
  public void makePayment();
}

(2)然后定义两个继承该接口的具体的实现化角色:
//银行卡支付
public class CreditCardPayment implements Payment{
  @Override
  public void makePayment() {
    System.out.println("使用银行卡支付。");
  }
}

//微信支付
public class WeiXinPayment implements Payment{
  @Override
  public void makePayment() {
    System.out.println("使用微信支付。");
  }
}

(3)接着定义抽象化角色,即支付处理抽象类 PaymentProcessor,其内部包含一个对实现化对象的引用。
// 支付处理抽象类
public abstract class PaymentProcessor {
  //支付方式的引用
  protected Payment payment;

  public PaymentProcessor(Payment payment) {
    this.payment = payment;
  }

  //处理付款行为
  public abstract void processPayment();
}

(4)然后定义两个继承该抽象类的扩展抽象化角色,其内部实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
// 线上支付处理
public class OnlinePaymentProcessor extends PaymentProcessor{
  public OnlinePaymentProcessor(Payment payment) {
    super(payment);
  }

  @Override
  public void processPayment() {
    System.out.println("开始线上付款。");
    payment.makePayment();
    System.out.println("线上付款成功。");
  }
}

// 店内支付处理
public class InStorePaymentProcessor extends PaymentProcessor{
  public InStorePaymentProcessor(Payment payment) {
    super(payment);
  }

  @Override
  public void processPayment() {
    System.out.println("开始店内付款。");
    payment.makePayment();
    System.out.println("店内付款成功。");
  }
}

(5)最后测试一下,使用组合关系来结合抽象部分和实现部分:
public class Test {
  public static void main(String[] args) {
    //使用银行卡在店内付款
    PaymentProcessor paymentProcessor1 = new InStorePaymentProcessor(new CreditCardPayment());
    paymentProcessor1.processPayment();

    //使用微信进行线上付款
    PaymentProcessor paymentProcessor2 = new OnlinePaymentProcessor(new WeiXinPayment());
    paymentProcessor2.processPayment();
  }
}

附一:JDK 中的桥接模式

(1)我们常用的 JDBC 就使用了桥接模式,JDBC 在连接数据库时,在各个数据库之间进行切换而不需要修改代码,因为 JDBC 提供了统一的接口,每个数据库都提供了各自的实现,通过一个叫作数据库驱动的程序来桥接即可。

(2)比如我们使用 JDBC 直连 MySQL 数据库的时候,会有这样一段代码:
Class.forName("com.mysql.cj.jdbc.Driver");//加载及注册JDBC驱动程序
String url = "jdbc:mysql://localhost:3306/sample_db?user=root&password=123";
Connection con = DriverManager.getConnection(url);
Statement stmt = con.createStatement();
String query = "select * from test";
ResultSet rs=stmt.executeQuery(query);
while(rs.next()) {
  rs.getString(1);
  rs.getInt(2);
}

(3)如果我们想要把 MySQL 数据库换成 Oracle 数据库,只需要把第一行代码中的 com.mysql.cj.jdbc.Driver 换成 oracle.jdbc.driver.OracleDriver 就可以了。这种优雅的实现数据库切换方式就是利用了桥接模式。
1,Class.forName("com.mysql.cj.jdbc.Driver") 作用有两个:
  • 要求 JVM 查找并加载指定的 Driver 类。
  • 执行该类的静态代码,也就是将 MySQL Driver 注册到 DriverManager 类中。
2,当我们把具体的 Driver 实现类(比如 com.mysql.cj.jdbc.Driver)注册到 DriverManager 之后,后续所有对 JDBC 接口的调用,都会委派到对具体的 Driver 实现类来执行。而 Driver 实现类都实现了相同的接口(java.sql.Driver),这也是可以灵活切换 Driver 的原因。

(4)桥接模式通过组合/聚合关系代替继承关系,实现抽象化和实现化部分的解耦。桥接模式中的实现化(Implementor)角色对应下图的 Driver 接口,具体实现化(Concrete Implementor)角色对应 MysqlDriverOracleDriverMariadbDriver,扩展抽象化 (Refined Abstraction)角色对应 DriverManager,但没有抽象化(Abstraction)角色作为扩展抽象化角色的父类。
注意:桥接模式可以处理系统中存在两个或者两个以上独立变化的维度情况,而且这两个维度都需要进行扩展。但 JDBC 中只有 Driver 这一个变化维度,DriverManager 没有抽象化父类,它本身也没有任何子类,因此可以认为 JDBC 使用的是一种简化的桥接模式。

附二:Spring 中的桥接模式

(1)Spring 框架中的日志处理系统使用了桥接模式的思想,它将日志的输出抽象出来,用接口来定义日志的输出行为,而具体的日志实现则是实现这个接口的类。这样就使得日志的输出与具体的日志实现可以独立变化。
例如,Spring 使用 org.slf4j.Logger 接口来定义日志的输出行为,而具体的日志实现则可以是 log4jlogback 或者 JDK Logging 等。这样,在使用 Spring 时,可以通过配置不同的日志实现来达到使用不同日志系统的目的。

(2)下面的代码中,MyClass 类使用了 org.slf4j.Logger 接口来记录日志。使用了 LoggerFactory.getLogger(MyClass.class) 来获取一个 logger 实例,并且在类中使用了 logger.debug() logger.info() 来记录日志。无论是使用 log4jlogback 还是 JDK Logging 等日志系统,我们都可以通过配置不同的日志实现来实现使用不同日志系统的目的,而不用改变 MyClass 类的代码。
public class MyClass {
    private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
    
    public void doSomething() {
        logger.debug("Doing something");
        //...
        logger.info("Something done");
    }
}
评论

全部评论(0)

回到顶部