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 Color {
public void getColor();
}
public class Red implements Color {
@Override
public String getColor() {
return "红";
}
}
如果想要红色的圆,这时我们很容易想到两种设计方案:
1、为了复用形状类,将每种形状定义为父类,每种不同颜色的图形继承自其形状父类。
class RedRound extends Round {
@Override
public void draw() {
System.out.println("绘制红色的圆形");
}
}
2、为了复用颜色类,将每种颜色定义为父类,每种不同颜色的图形继承自其颜色父类。
public class RedRound extends Red {
@Override
public String getColor() {
return "绘制红色的圆形";
}
}
一旦有5个颜色5个形状,就要写25个类!!!这时使用继承很容易造成子类越来越多。
形状和颜色,都是图形的两个属性。他们两者的关系是平等的,所以不属于继承关系。
更好的的实现方式是:将形状和颜色分离,根据需要对形状和颜色进行组合,这就是桥接模式的思想。
下面是理解过程
颜色的接口
public interface Color {
public void applyColor();
}
颜色的实现类
public class GreenColor implements Color {
public void applyColor(){
System.out.println("green.");
}
}
public class RedColor implements Color {
public void applyColor(){
System.out.println("red.");
}
}
将形状和颜色分离,根据需要对形状和颜色进行组合,代码里体现就是把原来两个类(Is-a
)继承关系,现在变成了组合 Has-a
的关系。
在每个形状类中桥接 Color 接口:
public abstract class Shape {
protected IColor color;
public Shape(IColor c) {
this.color = c;
}
public abstract void applyColor();
}
这样就是 将抽象与其实现解耦,Shape 里不关心 Color 具体实现,只是使用Color 接口的方法,Color 因为是接口所以具体实现有很灵活
此时各种颜色可以和各种形状组合,就是 多对多
的关系了。
public class Pentagon extends Shape {
public Pentagon(IColor c) {
super(c);
}
@Override
public void applyColor() {
System.out.println("Pentagon filled with color!! ");
color.applyColor();
}
}
public class Triangle extends Shape {
public Triangle(IColor c) {
super(c);
}
@Override
public void applyColor() {
System.out.println("Triangle 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 Display {
private IPrint impl;
public Display(IPrint 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 MultiDisplay extends Display {
public MultiDisplay(IPrint impl) {
super(impl);
}
public void multiDisplay(int times) {
open();
for (int i = 0; i < times; i++) {
print();
}
close();
}
}
以下为类的实现层次结构
public interface IPrint {
void rawOpen();
void rawPrint();
void rawClose();
}
public class StringPrintImpl implements IPrint {
private final String string;
private final int width;
public StringPrintImpl(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++) {
System.out.print("-");
}
System.out.println("+");
}
}
使用
Display d1 = new Display(new StringPrintImpl("Hello, China."));
d1.display();
Display d2 = new MultiDisplay(new StringPrintImpl("Hello, World."));
d2.display();
MultiDisplay d3 = new MultiDisplay(new StringPrintImpl("Hello, Universe."));
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就是桥,如下图所示。