设计模式之结构型模式中的桥接模式(Bridge Pattern)

Bridge Pattern

一、基本介绍

    桥接模式(Bridge Pattern)是一种结构型设计模式,基于类的最小设计原则,通过使用封装、聚合及继承等方式让不同的类承担不同的职责,从而把抽象(Abstraction)和实现(Implementation)分离开放在不同的层次中,使得各部分保持独立更加利于扩展。一句话来说,就是在抽象与实现之间提供一个桥梁,降低二者耦合度,达到二者可以独立变化而不互相影响的目的。

二、从“类爆炸”说起

     🌰 假如,现在有这样一个业务场景,需要对不同颜色不同品牌的手机实现相应的功能,如打电话、发短信、上网等。
    如果使用传统的方式,可能是这样的,类图如下:
传统方法对应的类图

    从以上的类图中,可以看出存在的问题,首先,这种方式会带来扩展性的问题,随着功能和手机品牌的增加,需要维护的类的数量也会增加,扩展性极差😖,产生“类爆炸”;其次,从类的最小设计原则考虑,当需要手机的颜色时,同时还需增加所有品牌的手机,这种设计违反了单一职责原则

三、桥接模式

    那么,基于以上两点的考虑,借鉴单一职责原则的核心思想,尝试将对象解耦,将类的功能职责粒度分解细化,通过使用封装、聚合及继承等行为让不同的类承担不同的职责。

3.1 采用桥接模式解决问题

  • 定义手机品牌接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 手机品牌接口
    *
    * @author sunys
    */
    public interface PhoneBrand {
    /**
    * Call.
    * 打电话
    */
    void call();

    /**
    * Send message.
    * 发短信
    */
    void sendMessage();

    /**
    * Go online.
    * 上网
    */
    void goOnline();
    }
  • 不同品牌手机接口的接口实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * @author sunys
    * HuaWei品牌接口实现
    */
    public class HuaWeiPhoneBrandImpl implements PhoneBrand {
    @Override
    public void call() {
    System.out.println("Call use HuaWei phone.");
    }

    @Override
    public void sendMessage() {
    System.out.println("SendMessage use HuaWei phone.");
    }

    @Override
    public void goOnline() {
    System.out.println("GoOnline use HuaWei phone.");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * @author sunys
    * Apple品牌接口实现
    */
    public class ApplePhoneBrandImpl implements PhoneBrand {
    @Override
    public void call() {
    System.out.println("Call use Apple phone.");
    }

    @Override
    public void sendMessage() {
    System.out.println("SendMessage use Apple phone.");
    }

    @Override
    public void goOnline() {
    System.out.println("GoOnline use Apple phone.");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * @author sunys
    * XiaoMi品牌接口实现
    */
    public class XiaoMiPhoneBrandImpl implements PhoneBrand {
    @Override
    public void call() {
    System.out.println("Call use XiaoMi phone.");
    }

    @Override
    public void sendMessage() {
    System.out.println("SendMessage use XiaoMi phone.");
    }

    @Override
    public void goOnline() {
    System.out.println("GoOnline use XiaoMi phone.");
    }
    }
  • 创建手机这个抽象类,即“桥”

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    /**
    * @author sunys
    * 手机抽象类 相当于“桥”
    */
    public abstract class Phone {

    /**
    * PhoneBrand 类聚合
    */
    private PhoneBrand phoneBrand;

    public Phone(PhoneBrand phoneBrand) {
    super();
    this.phoneBrand = phoneBrand;
    }

    protected void call() {
    this.phoneBrand.call();
    }


    protected void sendMessage() {
    this.phoneBrand.sendMessage();
    }

    protected void goOnline() {
    this.phoneBrand.goOnline();
    }
    }

  • 不同颜色的具体手机

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    /**
    * The type Phone black.
    * @author sunys
    */
    public class PhoneBlack extends Phone{
    /**
    * Instantiates a new Phone black.
    *
    * @param phoneBrand the phone brand
    */
    public PhoneBlack(PhoneBrand phoneBrand) {
    super(phoneBrand);
    }

    @Override
    public void call() {
    super.call();
    System.out.println("The phone of call is Black.");
    }

    @Override
    public void sendMessage() {
    super.sendMessage();
    System.out.println("The phone of sendMessage is Black.");
    }

    @Override
    public void goOnline() {
    super.goOnline();
    System.out.println("The phone of goOnline is Black.");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    /**
    * The type Phone red.
    *
    * @author sunys
    */
    public class PhoneRed extends Phone{
    /**
    * Instantiates a new Phone red.
    *
    * @param phoneBrand the phone brand
    */
    public PhoneRed(PhoneBrand phoneBrand) {
    super(phoneBrand);
    }

    @Override
    public void call() {
    super.call();
    System.out.println("The phone of call is Red.");
    }

    @Override
    public void sendMessage() {
    super.sendMessage();
    System.out.println("The phone of sendMessage is Red.");
    }

    @Override
    public void goOnline() {
    super.goOnline();
    System.out.println("The phone of goOnline is Red.");
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    /**
    * The type Phone white.
    *
    * @author sunys
    */
    public class PhoneWhite extends Phone{
    /**
    * Instantiates a new Phone white.
    *
    * @param phoneBrand the phone brand
    */
    public PhoneWhite(PhoneBrand phoneBrand) {
    super(phoneBrand);
    }

    @Override
    public void call() {
    super.call();
    System.out.println("The phone of call is White.");
    }

    @Override
    public void sendMessage() {
    super.sendMessage();
    System.out.println("The phone of sendMessage is White.");
    }

    @Override
    public void goOnline() {
    super.goOnline();
    System.out.println("The phone of goOnline is White.");
    }
    }
  • 客户端调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * The type Client.
    *
    * @author sunys
    */
    public class Client {
    /**
    * Bridge test.
    */
    public static void bridgeTest() {
    PhoneRed phoneRed = new PhoneRed(new HuaWeiPhoneBrandImpl());
    phoneRed.call();
    phoneRed.sendMessage();
    phoneRed.goOnline();
    PhoneBlack phoneBlack = new PhoneBlack(new HuaWeiPhoneBrandImpl());
    phoneBlack.call();
    phoneBlack.sendMessage();
    phoneBlack.goOnline();
    PhoneWhite phoneWhite = new PhoneWhite(new XiaoMiPhoneBrandImpl());
    phoneWhite.call();
    phoneWhite.sendMessage();
    phoneWhite.goOnline();
    }
    }

        至此,解决问题的方案改进完毕,通过这种方式,如果要添加新品牌的手机,只需让其实现PhoneBrand类即可;如果需要增加手机的颜色,只需让其继承Phone类即可。看下类图,如下:
    桥接模式方案类图
        在这个方案中,Phone这个抽象类是所有具体手机的父类,也是PhoneBrand类聚合的对象,起到了关键的“桥”的作用。基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责,将抽象与实现分离开保证不同层次独立改变互不影响更,从而极大的提高了系统的灵活性,有利于扩展。

四、桥接模式在JDBC中的应用

  • 首先,来看一段客户端调用JDBC连接数据库的过程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /**
    * The type Client.
    *
    * @author sunys
    */
    public class Client {

    /**
    * Jdbc test.
    */
    public static void jdbcTest(){
    try {
    // 1. 注册驱动
    Class.forName("com.mysql.jdbc.Driver");
    //2. 创建一个连接对象
    Connection conn = DriverManager.getConnection("url","user","password");
    //3. 创建一个sql语句的发送命令对象
    Statement stmt = conn.createStatement();
    // 4. 执行sql,拿到查询的结果集对象
    ResultSet rs = stmt.executeQuery("s");
    //5. 输出结果集的数据
    while(rs.next()){
    System.out.println(rs);
    }
    //6. 关闭连接,命令对象以及结果集。
    rs.close();
    stmt.close();
    conn.close();
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    } catch (SQLException throwables) {
    throwables.printStackTrace();
    }
    }
    }
  • 然后,看下相关类的UML类图
    桥接模式在JDBC中的应用类图

    从UML类图可以看出,com.mysql.jdbc.Driver类是java.sql.Driver接口的实现类,注册驱动时,会执行com.mysql.jdbc.Driver类的静态块,该静态代码块中调用java.sql.DriverManager#registerDriver(java.sql.Driver)方法,将Driver对象注册到 DriverManager中,DriverManager就相当于是“桥”;调用java.sql.DriverManager#getConnection(java.lang.String, java.lang.String, java.lang.String)创建一个连接对象时,会根据驱动的类型调用不同类型数据库(mysql、oracle等)实现的 Driver 的 connect() 方法,最终获得连接对象。

附:本次演示的项目地址
https://github.com/syshlang/java-design-patterns