Gof 原文
Decouple an abstraction from its implementation so that the two can varyindependently.
当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。 桥接模式适用于以下几种业务场景。 (1)在抽象和具体实现之间需要增加更多灵活性的场景。 (2)一个类存在两个(或多个)独立变化的维度,而这两个(或多个)维度都需要独立进行扩展。 (3)不希望使用继承,或因为多层继承导致系统类的个数剧增。
继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好地实现代码复用(封装)的功能,但这也是继承的一大缺点。因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。 因此,在设计模式中,有一个原则为优先使用组合/聚合,而不是继承。
The implementation of bridge design pattern follows the notion to prefer Composition over inheritance. 桥接设计模式的实现遵循组合优于继承的概念。
类内部具备两种或多种变化维度时,比如图形的形状和颜色。
public interface Shape {
void draw();
}
class Round implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}
public interface A1_Color {
public void getColor();
}
public class Red implements Color {
@Override
public String getColor() {
return "红";
}
}
如果想要红色的圆
这时我们很容易想到两种设计方案:
- 为了复用形状类,将每种形状定义为父类,每种不同颜色的图形继承自其形状父类。
- 为了复用颜色类,将每种颜色定义为父类,每种不同颜色的图形继承自其颜色父类。
class RedRound extends Round {
@Override
public void draw() {
System.out.println("绘制红色的圆形");
}
}
或者
public class RedRound extends Red {
@Override
public String getColor() {
return "绘制红色的圆形";
}
}
一旦有5个颜色5个形状,就要写25个类!!!
这时使用继承很容易造成子类越来越多。
形状和颜色,都是图形的两个属性。他们两者的关系是平等的,所以不属于继承关系。更好的的实现方式是:将形状和颜色分离,根据需要对形状和颜色进行组合,这就是桥接模式的思想。
下面是理解过程
颜色的接口
public interface A1_Color {
public void applyColor();
}
颜色的实现类
public class A5_RedColor implements A1_Color{
public void applyColor(){
System.out.println("red.");
}
}
public class A6_GreenColor implements A1_Color{
public void applyColor(){
System.out.println("green.");
}
}
将形状和颜色分离,根据需要对形状和颜色进行组合,代码里体现就是把原来两个类(RedRound extends Red)继承关系,现在变成了组合就是 Has-a 的关系。
public abstract class A2_Shape {
protected A6_GreenColor color;
...
}
此时就不是继承关系了而是组合关系了,一个颜色可以和各种形状组合,是一对多的关系。
中文一些解释里,将抽象与其实现解耦,将抽象部分与它的实现部分分离,Decouple an abstraction from its implementation翻译过来也是把抽象从实现里解耦。
这些名词说的太难理解了,不知道具体啥操作,后来慢慢有点体会,思路如下:
上述代码里protected A6_GreenColor color;就是抽象和实现绑定了,因为A6_GreenColor 是A1_Color的实现
如果在每个形状类中桥接 Color 接口:
public abstract class A2_Shape {
//Composition - implementor
protected A1_Color color;
//constructor with implementor as input argument
public A2_Shape(A1_Color c){
this.color=c;
}
abstract public void applyColor();
}
这样就是 将抽象与其实现解耦,A2_Shape 里不关心 A1_Color 具体实现,只是使用A1_Color 接口的方法,A1_Color 因为是接口所以具体实现有很灵活
此时各种颜色可以和各种形状组合,就是 多对多 的关系了。
protected A1_Color color;此时的 color 就是颜色和形状组合的桥梁。
public class A3_Triangle extends A2_Shape{
public A3_Triangle(A1_Color c) {
super(c);
}
@Override
public void applyColor() {
System.out.println("Triangle filled with color!! ");
color.applyColor();
}
}
public class A4_Pentagon extends A2_Shape{
public A4_Pentagon(A1_Color c) {
super(c);
}
@Override
public void applyColor() {
System.out.println("Pentagon filled with color!! ");
color.applyColor();
}
}
测试
A2_Shape tri = new A3_Triangle(new A5_RedColor());
tri.applyColor();
A2_Shape pent = new A4_Pentagon(new A6_GreenColor());
pent.applyColor();
Triangle filled with color!!
red.
Pentagon filled with color!!
green.
上文是读完下文修改的版本,下文才是思维源头(图解设计模式里的文章)
先介绍两个概念
希望增加新功能时
想给一个类增加新功能时,会创建这个类的子类增加新的方法。
- 父类具有基本功能
- 在子类中增加新的功能
- 以上这种层次结构被称为类的功能层次结构
希望增加新的实现时
- 父类通过声明抽象方法来定义接口(API)
- 子类通过实现具体方法来实现接口(API)
- 这种层次结构称为类的实现层次结构
当类的层次结构只有一层时,功能层次结构与实现层次结构是混杂在一个层次结构中的,这样容易使类的层次结构变得复杂,因为要把他们分开,但是分开后会缺少联系,多以要在他们之间搭一座桥,这就是Bridge模式的作用
Bridge模式的作用就是连接类的功能层次结构和类的实现层次结构
public class B1_Display {
private DisplayImpl impl;
public B1_Display(DisplayImpl impl) {
this.impl = impl;
}
public void open() {
impl.rawOpen();
}
public void print() {
impl.rawPrint();
}
public void close() {
impl.rawClose();
}
public final void display() {
open();
print();
close();
}
}
注意:private DisplayImpl impl; 其中impl就是两个层次结构的桥梁
子类增加新功能,此为类的功能层次结构的体现
public class B2_CountDisplay extends B1_Display {
public B2_CountDisplay(DisplayImpl impl) {
super(impl);
}
public void multiDisplay(int times) { // 循环显示times次
open();
for (int i = 0; i < times; i++) {
print();
}
close();
}
}
以下为类的实现层次结构
public abstract class B3_DisplayImpl {
public abstract void rawOpen();
public abstract void rawPrint();
public abstract void rawClose();
}
public class B4_StringDisplayImpl extends B3_DisplayImpl {
private String string; // 要显示的字符串
private int width; // 以字节单位计算出的字符串的宽度
public B4_StringDisplayImpl(String string) { // 构造函数接收要显示的字符串string
this.string = string; // 将它保存在字段中
this.width = string.getBytes().length; // 把字符串的宽度也保存在字段中,以供使用。
}
public void rawOpen() {
printLine();
}
public void rawPrint() {
System.out.println("|" + string + "|"); // 前后加上"|"并显示
}
public void rawClose() {
printLine();
}
private void printLine() {
System.out.print("+"); // 显示用来表示方框的角的"+"
for (int i = 0; i < width; i++) { // 显示width个"-"
System.out.print("-"); // 将其用作方框的边框
}
System.out.println("+"); // 显示用来表示方框的角的"+"
}
}
private DisplayImpl impl; 其中impl把两个两种层次结构给连接起来了
使用
public static void main(String[] args) {
B1_Display d1 = new B1_Display(new B4_StringDisplayImpl("Hello, China."));
B1_Display d2 = new B2_CountDisplay(new B4_StringDisplayImpl("Hello, World."));
B2_CountDisplay d3 = new B2_CountDisplay(new B4_StringDisplayImpl("Hello, Universe."));
d1.display();
d2.display();
d3.display();
d3.multiDisplay(5);
}
结果
+-------------+
|Hello, China.|
+-------------+
+-------------+
|Hello, World.|
+-------------+
+----------------+
|Hello, Universe.|
+----------------+
+----------------+
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
+----------------+
桥接模式在JDK源码中的应用
大家非常熟悉的JDBC API,其中有一个Driver类就是桥接对象。 我们都知道,在使用的时候通过Class.forName()方法可以动态加载各个数据库厂商实现的Driver类。 以MySQL的实现为例,具体客户端应用代码如下。
Class.forName("com.mysql.jdbc.Driver");
conn = (Connection) DriverManager.getConnection("jdbc:mysql://localhost:3306/ssm", "root", "123456");
ps = (PreparedStatement) conn.prepareStatement("select * from flower");
rs = ps.executeQuery();
Driver在JDBC中并没有做任何实现,具体的功能实现由各厂商完成。 我们以MySQL的实现为例
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!");
}
}
}
当我们执行Class.forName("com.mysql.jdbc.Driver")方法的时候,就会对类进行加载,就会执行com.mysql.jdbc.Driver类的 静态块 中的代码。 而静态块中的代码只是调用了一下DriverManager的registerDriver()方法,然后将Driver对象注册到DriverManager中。 继续跟进DriverManager类,来看相关的代码。
public class DriverManager {
...
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
...
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
...
...
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 {
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
...
接下来继续执行客户端代码的第二步,调用DriverManager的getConnection()方法获取连接对象
public class DriverManager {
...
@CallerSensitive
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 {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
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());
}
}
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
...
在getConnection()中,又会调用各自厂商实现的Driver的connect()方法获得连接对象。这样就巧妙地避开了使用继承,为不同的数据库提供了相同的接口。
JDBC API中的DriverManager就是桥,如下图所示。