传统方式解决手机操作问题

现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:

UML 类图

问题分析

  1. 扩展性问题(类爆炸):如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类;同样如果我们增加一个手机品牌,也要在各个手机样式类下增加
  2. 违反了单一职责原则:当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本
  3. 解决方案——使用桥接模式

桥接模式基本介绍

  1. 桥接模式(Bridge模式):一种结构型设计模式:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变
  2. Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责
  3. 它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展

原理类图

原理类图说明

  • Client:桥接模式的调用者
  • Abstraction:抽象化角色,定义抽象类,包含一个对实现化对象的引用(组合)
  • RefinedAbstraction:扩展抽象化角色,实现抽象化角色的子类,由此通过组合关系调用实现化角色中的业务方法
  • Implementor:实现化角色的接口,供扩展抽象化角色调用
  • ImplementorA、ImplementorB:实现化角色的具体实现
  • 这里的抽象类和接口是聚合的关系,也是调用者和被调用者的关系

桥接模式解决手机操作问题

UML 类图

核心代码

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// 行为接口——品牌接口
public interface Branch {
void open();

void call();

void close();
}
// 行为实现类——华为品牌
public class Huawei implements Branch {
@Override
public void open() {
System.out.println("华为手机开机");
}

@Override
public void call() {
System.out.println("华为手机打电话");
}

@Override
public void close() {
System.out.println("华为手机关机");
}
}
// 行为实现类——小米品牌
public class Xiaomi implements Branch {
@Override
public void open() {
System.out.println("小米手机开机");
}

@Override
public void call() {
System.out.println("小米手机打电话");
}

@Override
public void close() {
System.out.println("小米手机关机");
}
}
// 行为实现类——苹果品牌
public class iPhone implements Branch {
@Override
public void open() {
System.out.println("苹果手机开机");
}

@Override
public void call() {
System.out.println("苹果手机打电话");
}

@Override
public void close() {
System.out.println("苹果手机关机");
}
}

// 桥接类——手机抽象类
public abstract class Phone {
private Branch branch;

public Phone(Branch branch) {
this.branch = branch;
}

public void open() {
branch.open();
}

public void call() {
branch.call();
}

public void close() {
branch.close();
}
}
// 桥接子类——翻盖式手机
public class FlipPhone extends Phone {
public FlipPhone(Branch branch) {
super(branch);
System.out.println("翻盖式手机");
}

@Override
public void open() {
super.open();
}

@Override
public void call() {
super.call();
}

@Override
public void close() {
super.close();
}
}
// 桥接子类——滑盖式手机
public class SlidePhone extends Phone {
public SlidePhone(Branch branch) {
super(branch);
System.out.println("滑盖式手机");
}

@Override
public void open() {
super.open();
}

@Override
public void call() {
super.call();
}

@Override
public void close() {
super.close();
}
}
// 桥接子类——直立式手机
public class UprightPhone extends Phone {
public UprightPhone(Branch branch) {
super(branch);
System.out.println("直立式手机");
}

@Override
public void open() {
super.open();
}

@Override
public void call() {
super.call();
}

@Override
public void close() {
super.close();
}
}

JDK 源码分析

JDBC 的 Driver 接口:如果从桥接模式来看,Driver 就是一个接口,下面可以有 MySQL 的 Driver、Oracle 的 Driver,这些就可以当做实现接口类,查看对应的 com.mysql.cj.jdbc.Driver路径下的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MYSQL的  com.mysql.cj.jdbc.Driver

package com.mysql.cj.jdbc;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}

是通过静态方法调用registerDriver()方法来将MySQL驱动注入到DriverManagerregisterDriver()方法具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 {
/* registeredDrivers是一个list,用DriverInfo实例封装Driver */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);

}

Connection 继承体系

1
2
3
4
5
public interface Connection  extends Wrapper, AutoCloseable {

Statement createStatement() throws SQLException;
//...
}

DriverManager 结构

1
2
3
4
5
public class DriverManager {
// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
//...
}

说明

  • Driver 和 Connection 之间是通过 DriverManager 类进行桥连接的

  • Connection接口是和特定数据库的连接会话,不同的数据库的连接会话都不相同:

  • 通过DriverManager中的getConnection方法,从registeredDrivers进行选择对应数据库驱动下的连接实例

    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
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    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()));
    }

    // 实际上调用的是下面的静态方法getConnection
    // Worker method called by the public getConnection() methods.
    private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    /*
    * When callerCl is null, we should check the application's
    * (which is invoking this class indirectly)
    * classloader, so that the JDBC driver class outside rt.jar
    * can be loaded from here.
    */
    //....

    for(DriverInfo aDriver : registeredDrivers) {
    // If the caller does not have permission to load the driver then
    // skip it.
    if(isDriverAllowed(aDriver.driver, callerCL)) {
    try {
    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);
    }
    } catch (SQLException ex) {
    if (reason == null) {
    reason = ex;
    }
    }

    } else {
    println(" skipping: " + aDriver.getClass().getName());
    }
    }
    // ...
    }
  • Connection接口的具体实现部分,MySQL的连接是通过两层实现完成抽象部分的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class ConnectionImpl implements JdbcConnection, SessionEventListener, Serializable {
    private static final long serialVersionUID = 4009476458425101761L;
    private static final SQLPermission SET_NETWORK_TIMEOUT_PERM = new SQLPermission("setNetworkTimeout");
    //...
    }
    public interface JdbcConnection extends Connection, MysqlConnection, TransactionEventHandler {
    JdbcPropertySet getPropertySet();

    void changeUser(String var1, String var2) throws SQLException;
    //...
    }

注意事项和细节

  1. 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来。这有助于系统进行分层设计,从而产生更好的结构化系统
  2. 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成
  3. 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
  4. 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
  5. 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的后限性,即需要有这样的应用场景

桥接模式其他应用场景

对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用

常见的应用场景

  1. JDBC 驱动程序

  2. 银行转账系统

    • 转账分类:网上转账、柜台转账、AMT 转账
    • 转账用户类型:普通用户、银卡用户、金卡用户
  3. 消息管理

    • 消息类型:即时消息、延时消息
    • 消息分类:手机短信、邮件消息、QQ消息…