设计模式之结构型模式中的桥接模式(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