桥接模式

桥接模式是一种结构型设计模式,英文是 Bridge Design Pattern。在 GoF 的《设计模式》一书中,桥接模式是这么定义的:Decouple an abstraction from its implementation so that the two can vary independently。翻译成中文就是:将抽象和实现解耦,让它们可以独立变化。单从官方概念来理解,相当晦涩,通常对抽象与实现的第一反应就是接口(或抽象类)与实现类的继承关系,接口和实现独立变化就更加让人难以理解。

关于桥接模式,还有另外一种理解方式:一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。再配合类图(https://zh.wikipedia.org/wiki/%E6%A9%8B%E6%8E%A5%E6%A8%A1%E5%BC%8F)就比较容易理解一些。

 

桥接模式_第1张图片

桥接模式所涉及的角色有:

  • 抽象(Abstraction)角色:抽象角色,采用抽象类方式,并持有一个实现角色对象的引用。
  • 具体抽象(RefinedAbstraction)角色:具体抽象角色具体实现。
  • 实现(Implementor)角色:给出实现角色的接口。实现化角色应当只给出抽象层操作,抽象角色持有抽象角色并只给出基于抽象角色的操作。
  • 具体实现(ConcreteImplementor)角色:这个角色给出实现角色接口的具体实现。

可以看到抽象和实现是两个角色,是调用者和被调用者的关系,通过组合关系使抽象角色持有实现角色,通过组合关系来替代继承关系,避免继承层次过多。

接下来通过代码理解一下桥接模式,当我们办理银行卡时,有不同的银行(工商银行,建设银行)可选择,每个银行有不同的卡(储蓄卡,信用卡),所以银行和卡就相当于两个独立变化的维度,我们将银行和卡进行抽象,将银行作为抽象角色,将卡作为实现角色,通过组合的方式使银行持有卡的接口引用,将其组合在一起。

public class BridgePattern {

    /**
     * 银行抽象类,桥接模式的抽象部分
     */
    abstract static class Bank {
        protected Card card;

        public Bank(Card card) {
            this.card = card;
        }

        abstract Card applyCard();
    }

    /**
     * 建设银行实现类
     */
    static class CCBBank extends Bank {

        public CCBBank(Card card) {
            super(card);
        }

        @Override
        Card applyCard() {
            System.out.print("申请建设银行 ");
            card.applyCard();
            return card;
        }
    }

    /**
     * 工商银行实现类
     */
    static class ICBCBank extends Bank {

        public ICBCBank(Card card) {
            super(card);
        }

        @Override
        Card applyCard() {
            System.out.print("申请工商银行 ");
            card.applyCard();
            return card;
        }
    }

    /**
     * 银行账号,桥接模式的实现部分接口
     */
    interface Card {
        /**
         * 申请卡
         *
         * @return
         */
        Card applyCard();

        /**
         * 查看卡类型
         */
        void showCardType();
    }

    /**
     * 信用卡
     */
    static class CreditCard implements Card {
        @Override
        public CreditCard applyCard() {
            return new CreditCard();
        }

        @Override
        public void showCardType() {
            System.out.println("信用卡");
        }
    }

    /**
     * 储蓄卡
     */
    static class DebitCard implements Card {
        @Override
        public DebitCard applyCard() {
            return new DebitCard();
        }

        @Override
        public void showCardType() {
            System.out.println("储蓄卡");
        }
    }

    public static void main(String[] args) {
        Bank icbcBank = new ICBCBank(new CreditCard());
        Card icbcCard = icbcBank.applyCard();
        icbcCard.showCardType();

        Bank ccbBank = new CCBBank(new DebitCard());
        Card ccbCard = ccbBank.applyCard();
        ccbCard.showCardType();
    }

}

输出结果

申请工商银行 信用卡
申请建设银行 储蓄卡

通过使用桥接模式,就使得银行和卡这两个维度可以独立变化,互不干扰。

JDBC数据库访问接口API正是经典的桥接模式实现,具体的代码如下所示:


Class.forName("com.mysql.jdbc.Driver");// 加载及注册JDBC驱动程序
String url = "jdbc:mysql://localhost:3306/mydb?user=root&password=****";
Connection con = DriverManager.getConnection(url);

如果我们想要把 MySQL 数据库换成 Oracle 数据库,只需要把第一行代码中的 com.mysql.jdbc.Driver 换成 oracle.jdbc.driver.OracleDriver 便可。

com.mysql.jdbc.Driver类代码实现如下:

package com.mysql.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

这里涉及另外一个知识,Class.forName和ClassLoader加载类的区别:ClassLoader只能将类加载到JVM方法区(JDK8为元数据区),但是Class.forName除了加载类外,还可以直接加载类中的静态代码块。

所以当执行 Class.forName(“com.mysql.jdbc.Driver”) 这条语句的时候,会将 com.mysql.jdbc.Driver 注册到 DriverManager 类中。

java.sql.DriverManager类代码精简后实现如下:

package java.sql;

public class DriverManager {

    private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>();

    private DriverManager() {
    }

    public static synchronized void registerDriver(java.sql.Driver driver)
            throws SQLException {

        registerDriver(driver, null);
    }

    public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if (driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }
        println("registerDriver: " + driver);
    }

    public static Connection getConnection(String url, String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();
        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }
        return (getConnection(url, info, Reflection.getCallerClass()));
    }

    private static Connection getConnection(String url, java.util.Properties info, Class caller) throws SQLException {
        for (DriverInfo aDriver : registeredDrivers) {
            println("    trying " + aDriver.driver.getClass().getName());
            Connection con = aDriver.driver.connect(url, info);
            if (con != null) {
                // Success!
                println("getConnection returning " + aDriver.driver.getClass().getName());
                return (con);
            }
        }
    }

}

可以看到注册的过程无非是把Driver对象存放进入了CopyOnWriteArrayList类型的registeredDrivers变量中,获取连接的时候,从registeredDrivers中取出对应的Driver并调用相应的connect方法进行连接。

JDBC 这个例子中,JDBC DriverManager就相当于“抽象”角色,java.sql.Driver相当于实现角色,具体的 Driver(比如com.mysql.jdbc.Driver)就相当于实现角色的具体实现。JDBC  DriverManager 和 Driver 独立开发,通过对象之间的组合关系,组装在一起。JDBC 的所有逻辑操作,最终都委托给 Driver 来执行。

JDBC的类族是由SUN公司设计了一套接口,再由各个数据库公司实现接口,我们在调用的过程中只需要使用接口去定义,然后在加载Driver的过程中底层代码会给我们选择好接口真正的实现类,以此来实现真正的数据库连接。

总体上来讲,桥接模式的原理比较难理解,但代码实现相对简单。在 GoF 的《设计模式》一书中,桥接模式被定义为:将抽象和实现解耦,让它们可以独立变化。还有另外一种更加简单的理解方式:一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。对于第一种 GoF 的理解方式,弄懂定义中“抽象”和“实现”两个概念,是理解它的关键。定义中的“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的角色,它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。而定义中的“实现”,也并非“接口的实现类”,而是一套独立的角色。“抽象”和“实现”独立开发,通过对象之间的组合关系,组装在一起。

桥接模式的优点使抽象和实现可以沿着各自的维度来变化,所谓抽象和实现沿着各自维度的变化,也就是说抽象和实现不再在同一个继承层次结构中,使它们各自都具有自己的子类,从而获得多维度组合对象。桥接模式可以取代多层继承方案,多层继承方案违背了“单一职责原则”,提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”。

桥接模式的缺点是原理比较难理解,会增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程。还要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也并非一件易事。

你可能感兴趣的:(设计模式,设计模式,java)