江头未是风波恶,别有人间行路难!

——辛弃疾《鹧鸪天》

## 设计模式-桥接模式

1. 案例引出桥接模式

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

PhoneANli

umlleitu

传统方案解决手机操作问题分析

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

2. 桥接模式

2.1 桥接模式基本介绍
  1. 桥接模式(Bridge 模式)是指:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变。
  2. 是一种结构型设计模式。
  3. Bridge 模式基于类的最小设计原则通过使用封装、聚合及继承等行为让不同的类承担不同的职责。它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展。
2.2 桥接模式原理类图
  1. Client 类:桥接模式的调用者
  2. 抽象类(Abstraction) :充当桥接类
  3. RefinedAbstraction类 : 是 Abstraction 抽象类的子类
  4. Implementor接口 : 行为实现类的接口
  5. ConcreteImplementorA/B类 :行为的具体实现类
  6. 从 UML 图:这里的抽象类和接口是聚合的关系,其实调用和被调用关系,重点是抽象类Abstraction,其维护了 Implementor接口 (即它的实现类 ConcreteImplementorA/B), 抽象类Abstraction与接口Implementor是聚合关系。

20200508211712743xx

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

使用桥接模式改进传统方式,让程序具有搞好的扩展性,利用程序维护。

qjmsumlleitu

2.4 代码实现之
抽象类Phone及其子类

注意Phone抽象类是怎么与Brand接口进行关联的。

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
// 抽象类Phone
public abstract class Phone {

//组合品牌
private Brand brand;

//构造器
public Phone(Brand brand) {
super();
this.brand = brand;
}

protected void open() {
this.brand.open();
}
protected void close() {
brand.close();
}
protected void call() {
brand.call();
}

}
// 直立样式手机
public class UpRightPhone extends Phone {

//构造器
public UpRightPhone(Brand brand) {
super(brand);
}

public void open() {
super.open();
System.out.println(" 直立样式手机 ");
}

public void close() {
super.close();
System.out.println(" 直立样式手机 ");
}

public void call() {
super.call();
System.out.println(" 直立样式手机 ");
}
}
//折叠式手机类,继承 抽象类 Phone
public class FoldedPhone extends Phone {

//构造器
public FoldedPhone(Brand brand) {
super(brand);
}

public void open() {
super.open();
System.out.println(" 折叠样式手机 ");
}

public void close() {
super.close();
System.out.println(" 折叠样式手机 ");
}

public void call() {
super.call();
System.out.println(" 折叠样式手机 ");
}
}
接口Brand及其子类
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
//接口
public interface Brand {
void open();
void close();
void call();
}

// Vivo类
public class Vivo implements Brand {

@Override
public void open() {
// TODO Auto-generated method stub
System.out.println(" Vivo手机开机 ");
}

@Override
public void close() {
// TODO Auto-generated method stub
System.out.println(" Vivo手机关机 ");
}

@Override
public void call() {
// TODO Auto-generated method stub
System.out.println(" Vivo手机打电话 ");
}

}

// XiaoMi类
public class XiaoMi implements Brand {

@Override
public void open() {
// TODO Auto-generated method stub
System.out.println(" 小米手机开机 ");
}

@Override
public void close() {
// TODO Auto-generated method stub
System.out.println(" 小米手机关机 ");
}

@Override
public void call() {
// TODO Auto-generated method stub
System.out.println(" 小米手机打电话 ");
}

}
调用端Client测试
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
public class Client {

public static void main(String[] args) {

//获取折叠式手机 (样式 + 品牌 )

Phone phone1 = new FoldedPhone(new XiaoMi());
phone1.open();
phone1.call();
phone1.close();
System.out.println("=======================");
Phone phone2 = new FoldedPhone(new Vivo());
phone2.open();
phone2.call();
phone2.close();

// 所有的直立式手机
Phone phone3 = new UpRightPhone(new XiaoMi());
phone3.open();
phone3.call();
phone3.close();

System.out.println("=======================");
Phone phone4 = new UpRightPhone(new Vivo());
phone4.open();
phone4.call();
phone4.close();
}
}
  1. 通过代码是不是可以领会到桥接模式的力量?
  2. 我们如果增加手机的品牌HUAWEI,我们只需要让其实现Brand类重写方法就可以了;
  3. 如果我们想增加全面屏手机FullScreenPhone类型,我们只需要让其继承Phone类然后重写其方法就行了;
2.5 桥接模式在 JDBC 的源码应用

使用JDBC的时候,你是否一直很困惑获取connection的过程和原理,为什么把不同的数据库驱动名称放到Class.forName("com.mysql.jdbc.Driver")中就能获取到对应的数据库连接呢?

通过Ctrl + H 查看。

image-20200621235556377

image-20200621235614099

Jdbc 的 Driver 接口,如果从桥接模式来看,Driver 就是一个接口,下面可以有 MySQL 的 Driver,Oracle 的Driver,这些就可以当做实现接口类。

通过Class.forName("com.mysql.jdbc.Driver")类加载的时候执行静态代码块将Driver注册到DriverManager中的registeredDrivers中,DriverManager中的registeredDrivers是个Driver容器,管理不同的Driver,这样具体的数据Driver实现就统一交给容器管理,客户端通过DriverManager执行验证连接,获取连接的操作。

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
public class DriverManager {


// 已注册的JDBC驱动程序列表
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

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

/* 如果驱动程序还没有添加到我们的列表中,请注册它 */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// 这是为了与原始驱动管理器兼容
throw new NullPointerException();
}

println("registerDriver: " + driver);

}

// ...

}

class DriverInfo {

final Driver driver;
DriverAction da;
DriverInfo(Driver driver, DriverAction action) {
this.driver = driver;
da = action;
}

@Override
public boolean equals(Object other) {
return (other instanceof DriverInfo)
&& this.driver == ((DriverInfo) other).driver;
}

@Override
public int hashCode() {
return driver.hashCode();
}

@Override
public String toString() {
return ("driver[className=" + driver + "]");
}

DriverAction action() {
return da;
}
}

image-20200621225049710

3.桥接模式的细节☆

  1. 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,这有助于系统进行分层设计,从而产生更好的结构化系统。
  2. 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。
  3. 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本。
  4. 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。
  5. 桥接模式要求正确识别出系统中两个独立变化的维度( 抽象、和实现),因此其使用范围有一定的局限性,即需要有这样的应用场景。
  6. 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
  7. 常见的应用场景
    1. JDBC 驱动程序
    2. 银行转账系统
      • 转账分类: 网上转账,柜台转账,AMT 转账
      • 转账用户类型:普通用户,银卡用户,金卡用户..
    3. 消息管理
      • 消息类型:即时消息,延时消息
      • 消息分类:手机短信,邮件消息,QQ 消息…