前言:
桥接模式也叫作桥梁模式,英文是 Bridge Design Pattern。是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。在 GoF 的《设计模式》一书中,桥接模式是这么定义的:“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就是:“将抽象和实现解耦,让它们可以独立变化。”关于桥接模式,很多书籍、资料中,还有另外一种理解方式:“一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”通过组合关系来替代继承关系,避免继承层次的指数级爆炸。(不常用)
目录
一、结构
二、代码实现
使用传统的解决方式:
采用桥接模式
/**
* @Description: 品牌接口
*
* @Author HJW
* @Date 2021/4/17
* @Version V1.0
**/
public interface Brand {
void call();
void on();
void off();
void online();
}
/**
* @Description: TODO
*
* @Author HJW
* @Date 2021/4/17
* @Version V1.0
**/
public class Apple implements Brand{
@Override
public void call() {
System.out.println("Apple call");
}
@Override
public void on() {
System.out.println("Apple on");
}
@Override
public void off() {
System.out.println("Apple off");
}
@Override
public void online() {
System.out.println("Apple online");
}
}
/**
* @Description: TODO
*
* @Author HJW
* @Date 2021/4/17
* @Version V1.0
**/
public class Huawei implements Brand{
@Override
public void call() {
System.out.println("Huawei call");
}
@Override
public void on() {
System.out.println("Huawei on");
}
@Override
public void off() {
System.out.println("Huawei off");
}
@Override
public void online() {
System.out.println("Huawei online");
}
}
/**
* @Description: 手机
*
* @Author HJW
* @Date 2021/4/17
* @Version V1.0
**/
public abstract class Phone {
private Brand brand;
public Phone(Brand brand) {
super();
this.brand = brand;
}
protected void call() {
this.brand.call();
}
protected void on() {
this.brand.on();
}
protected void off() {
this.brand.off();
}
protected void online() {
this.brand.online();
}
}
/**
* @Description: 直立手机
*
* @Author HJW
* @Date 2021/4/17
* @Version V1.0
**/
public class ErectPhone extends Phone {
public ErectPhone(Brand brand) {
super(brand);
}
@Override
public void call() {
super.call();
System.out.println("直立样式手机");
}
@Override
public void on() {
super.on();
System.out.println("直立样式手机");
}
@Override
public void off() {
super.off();
System.out.println("直立样式手机");
}
@Override
public void online() {
super.online();
System.out.println("直立样式手机");
}
}
/**
* @Description: 折叠手机
*
* @Author HJW
* @Date 2021/4/17
* @Version V1.0
**/
public class FoldPhone extends Phone {
public FoldPhone(Brand brand) {
super(brand);
}
@Override
public void call() {
super.call();
System.out.println("折叠样式手机");
}
@Override
public void on() {
super.on();
System.out.println("折叠样式手机");
}
@Override
public void off() {
super.off();
System.out.println("折叠样式手机");
}
@Override
public void online() {
super.online();
System.out.println("直立样式手机");
}
}
/**
* @Description: TODO
* @Author HJW
* @Date 2021/4/17
* @Version V1.0
**/
public class Client {
public static void main(String[] args){
Phone foldPhone = new FoldPhone(new Huawei());
foldPhone.call();
foldPhone.off();
foldPhone.online();
Phone phone = new ErectPhone(new Apple());
phone.on();
phone.call();
phone.off();
}
}
三、桥接模式的典型应用-JDBC 驱动
/**
* @Description: jdbc配置
*
* @Author HJW
* @Date 2021/4/19
* @Version V1.0
**/
public class JdbcConfig {
public void init() throws ClassNotFoundException, SQLException {
// 加载及注册JDBC驱动程序
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/demo_db?user=root&password=your_password";
Connection con = DriverManager.getConnection(url);
String query = "select * from test";
PreparedStatement stmt = con.prepareStatement(query);
ResultSet resultSet = stmt.executeQuery(query);
while(resultSet.next()) {
resultSet.getString(1);
resultSet.getInt(2);
}
}
}
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
结合 com.mysql.jdbc.Driver 的代码实现,我们可以发现,当执行 Class.forName(“com.mysql.jdbc.Driver”) 这条语句的时候,实际上是做了两件事情。第一件事情是要求 JVM 查找并加载指定的 Driver 类,第二件事情是执行该类的静态代码,也就是将 MySQL Driver 注册到 DriverManager 类中。现在,我们再来看一下,DriverManager 类是干什么用的。具体的代码如下所示。当我们把具体的 Driver 实现类(比如,com.mysql.jdbc.Driver)注册到 DriverManager 之后,后续所有对 JDBC 接口的调用,都会委派到对具体的 Driver 实现类来执行。而 Driver 实现类都实现了相同的接口(java.sql.Driver ),这也是可以灵活切换 Driver 的原因。
public class DriverManager {
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
//...
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
//...
public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {
if (driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver));
} else {
throw new NullPointerException();
}
}
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()));
}
//...
}
桥接模式的定义是“将抽象和实现解耦,让它们可以独立变化”。那弄懂定义中“抽象”和“实现”两个概念,就是理解桥接模式的关键。那在 JDBC 这个例子中,什么是“抽象”?什么是“实现”呢?实际上,JDBC 本身就相当于“抽象”。注意,这里所说的“抽象”,指的并非“抽象类”或“接口”,而是跟具体的数据库无关的、被抽象出来的一套“类库”。具体的 Driver(比如,com.mysql.jdbc.Driver)就相当于“实现”。注意,这里所说的“实现”,也并非指“接口的实现类”,而是跟具体数据库相关的一套“类库”。JDBC 和 Driver 独立开发,通过对象之间的组合关系,组装在一起。JDBC 的所有逻辑操作,最终都委托给 Driver 来执行。如下图:
四、桥接模式适合应用场景
- 如果你想要拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类), 可以使用桥接模式。
类的代码行数越多, 弄清其运作方式就越困难, 对其进行修改所花费的时间就越长。 一个功能上的变化可能需要在整个类范围内进行修改, 而且常常会产生错误, 甚至还会有一些严重的副作用。
桥接模式可以将庞杂类拆分为几个类层次结构。 此后, 你可以修改任意一个类层次结构而不会影响到其他类层次结构。 这种方法可以简化代码的维护工作, 并将修改已有代码的风险降到最低。
- 如果你希望在几个独立维度上扩展一个类, 可使用该模式。
桥接建议将每个维度抽取为独立的类层次。 初始类将相关工作委派给属于对应类层次的对象, 无需自己完成所有工作。
- 如果你需要在运行时切换不同实现方法, 可使用桥接模式。
当然并不是说一定要实现这一点, 桥接模式可替换抽象部分中的实现对象, 具体操作就和给成员变量赋新值一样简单。顺便提一句, 最后一点是很多人混淆桥接模式和策略模式的主要原因。
记住, 设计模式并不仅是一种对类进行组织的方式, 它还能用于沟通意图和解决问题。
五、 桥接模式优缺点
- 可以创建与平台无关的类和程序。
- 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。
- 开闭原则。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。
- 单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。
- XXX 对高内聚的类使用该模式可能会让代码更加复杂。
六、与其他模式的关系
-
桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。
-
桥接、 状态模式和策略模式 (在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。
-
你可以将抽象工厂模式和桥接搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。