“这是我参与8月更文挑战的第23天,活动详情查看:8月更文挑战”
一、什么是桥接模式?
桥接模式(Bridge Pattern)也称为桥梁模式,是将抽象部分与它的具体实现部分分离,使它们都可以独立的变化,属于结构型模式。
桥接模式主要是通过组合的方式建立两个类之间的联系,而不是继承。但又类似于多重继承方案,但是多重继承方案往往违背了类单一职责原则,其复用性比较差,桥接模式是比多重继承更好的替代方案。桥接模式的核心在于解耦抽象和实现。
二、桥接模式的应用场景
当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。桥接模式适用于以下几种业务场景:
- 在抽象和具体实现之间需要增加更多的灵活性的场景。
- 一个类存在两个(或多个)独立变化的维度,而这两个(或多个)维度都需要独立进行扩展。
- 不希望使用继承,或因为多层继承导致系统类的个数剧增。
桥接模式的一个常用使用场景就是为了替代继承。我们知道,继承拥有很多优点,比如抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的帮助我们实现代码复用(封装)的功能,但是同时,这也是继承的一大缺点。因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明了继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。。。因此,在设计模式中,有一个原则为:优先使用组合/聚合的方式,而不是继承。
在生活场景中的桥接模式也随处可见,比如连接两个空间维度的桥,比如链接虚拟网络与真实网络的连接。
三、桥接模式在业务场景中的应用
举个例子,我们平时办公的时候经常通过发邮件消息、短信消息或者系统内消息和同事进行沟通。尤其是在走一些审批流程的时候,我们需要记录这些过程以备查。我们根据消息的类别来划分的话,可以分为邮件消息、短信消息和系统内消息。但是,根据消息的紧急程度来划分的话,可以分为普通消息、紧急消息和特急消息。显然,整个消息系统可以划分为两个维度,如下图:
如果,我们用继承的话情况就复杂了,而且不利于扩展。邮件信息可以是普通的,也可以是紧急的,短信消息可以是普通的,也可以是紧急的。下面我们用桥接模式来解决这个问题:
首先创建一个IMessage接口担任桥接的角色:
/**
* 实现消息发送的统一接口
*/
public interface IMessage {
//要发送的消息的内容和接收人
void send(String message, String toUser);
}
创建邮件消息实现EmailMessage类:
/**
* 邮件短消息的实现类
*/
public class EmailMessage implements IMessage {
@Override
public void send(String message, String toUser) {
System.out.println(String.format("使用邮件短消息的方法,发送消息 %s 给 %s", message, toUser));
}
}
创建手机短信实现SmsMessage类:
/**
* 系统内短消息的实现类
* SMS(Short IMessage Service)短信息服务
*/
public class SmsMessage implements IMessage {
@Override
public void send(String message, String toUser) {
System.out.println(String.format("使用系统内部短消息的方法,发送消息 %s 给 %s", message, toUser));
}
}
然后,再创建桥接抽象角色AbstractMessage类:
/**
* 抽象类消息
*/
public abstract class AbstractMessage {
//持有一个实现部分的抽象
private IMessage iMessage;
//构造方法,传入实现部分的对象
public AbstractMessage(IMessage iMessage) {
this.iMessage = iMessage;
}
//发送消息,委派给实现部分的方法
public void sendMessage(String message, String toUser) {
iMessage.send(message, toUser);
}
}
创建具体实现普通消息NormalMessage类:
/**
* 普通消息类
*/
public class NormalMessage extends AbstractMessage {
//构造方法,传入实现部分的对象
public NormalMessage(IMessage iMessage) {
super(iMessage);
}
@Override
public void sendMessage(String message, String toUser) {
//对于普通消息,直接调用父类方法,发送消息即可
super.sendMessage(message, toUser);
}
}
创建具体实现紧急消息UrgencyMessage类:
/**
* 加急消息
*/
public class UrgencyMessage extends AbstractMessage {
//构造方法
public UrgencyMessage(IMessage iMessage) {
super(iMessage);
}
@Override
public void sendMessage(String message, String toUser) {
message = "加急:" + message;
super.sendMessage(message, toUser);
}
}
编写客户端测试代码:
public class Test {
public static void main(String[] args) {
IMessage message = new SmsMessage();
AbstractMessage abstractMessage = new NormalMessage(message);
abstractMessage.sendMessage("加班申请特批", "马总");
message = new EmailMessage();
abstractMessage = new UrgencyMessage(message);
abstractMessage.sendMessage("加班申请特批", "马总");
}
}
运行结果如下:
上面的案例中,我们采用桥接模式解耦了 “消息类型” 和 “消息紧急程度” 这两个独立变化的维度,后续如果有更多的消息类型,比如微信、钉钉等,那么直接新建一个类继承IMessage即可,如果是紧急程度需求增加,那么同样只需新建一个类实现AbstractMessage类即可。
四、桥接模式在源码中的应用
大家都熟悉的JDBC API,其中有一个Driver类就是桥接对象。我们都知道,我们在使用的时候通过Class.forName()方法可以动态加载各个数据库厂商实现的Driver类。具体客户端应用代码如下,以MySQL的实现为例:
public class Test {
public static void main(String[] args) throws Exception {
//1.加载驱动
Class.forName("com.mysql.jdbc.Driver");//反射机制加载驱动类
//2.获取连接Connection
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
//3.得到执行sql语句的对象Statement
Statement stmt = conn.createStatement();
//4.执行sql语句,并返回结果
ResultSet rs = stmt.executeQuery("select * from xxx");
}
}
首先,我们来看一下Driver接口的定义:
public interface Driver {
Connection connect(String url, java.util.Properties info)
throws SQLException;
boolean acceptsURL(String url) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
throws SQLException;
int getMajorVersion();
int getMinorVersion();
boolean jdbcCompliant();
public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}
Driver在JDBC中并没有做任何实现,具体的功能实现由各厂商完成,我们以MySQL的实例为例。
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()
}
}
当我们执行Class.forName("com.mysql.jdbc.Driver")方法的时候,就会执行com.mysql.jdbc.Driver这个类的静态代码块中的代码。而静态代码中的代码只是调用了一下DriverManager的registerDriver()方法,然后将Driver对象注册到DriverManager中。我们可以继续跟进到DriverManager这个类中,来看相关的代码:
public class DriverManager {
// List of registered JDBC drivers
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
private static volatile int loginTimeout = 0;
private static volatile java.io.PrintWriter logWriter = null;
private static volatile java.io.PrintStream logStream = null;
// Used in println() to synchronize logWriter
private final static Object logSync = new Object();
/* Prevent the DriverManager class from being instantiated. */
private DriverManager(){}
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
final static SQLPermission SET_LOG_PERMISSION =
new SQLPermission("setLog");
final static SQLPermission DEREGISTER_DRIVER_PERMISSION =
new SQLPermission("deregisterDriver");
public static java.io.PrintWriter getLogWriter() {
return logWriter;
}
public static void setLogWriter(java.io.PrintWriter out) {
SecurityManager sec = System.getSecurityManager();
if (sec != null) {
sec.checkPermission(SET_LOG_PERMISSION);
}
logStream = null;
logWriter = out;
}
@CallerSensitive
public static Connection getConnection(String url,
java.util.Properties info) throws SQLException {
return (getConnection(url, info, Reflection.getCallerClass()));
}
@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()));
}
@CallerSensitive
public static Connection getConnection(String url)
throws SQLException {
java.util.Properties info = new java.util.Properties();
return (getConnection(url, info, Reflection.getCallerClass()));
}
@CallerSensitive
public static Driver getDriver(String url)
throws SQLException {
println("DriverManager.getDriver(\"" + url + "\")");
Class<?> callerClass = Reflection.getCallerClass();
// Walk through the loaded registeredDrivers attempting to locate someone
// who understands the given URL.
for (DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerClass)) {
try {
if(aDriver.driver.acceptsURL(url)) {
// Success!
println("getDriver returning " + aDriver.driver.getClass().getName());
return (aDriver.driver);
}
} catch(SQLException sqe) {
// Drop through and try the next driver.
}
} else {
println(" skipping: " + aDriver.driver.getClass().getName());
}
}
println("getDriver: no suitable driver");
throw new SQLException("No suitable driver", "08001");
}
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 {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
@CallerSensitive
public static synchronized void deregisterDriver(Driver driver)
throws SQLException {
if (driver == null) {
return;
}
SecurityManager sec = System.getSecurityManager();
if (sec != null) {
sec.checkPermission(DEREGISTER_DRIVER_PERMISSION);
}
println("DriverManager.deregisterDriver: " + driver);
DriverInfo aDriver = new DriverInfo(driver, null);
if(registeredDrivers.contains(aDriver)) {
if (isDriverAllowed(driver, Reflection.getCallerClass())) {
DriverInfo di = registeredDrivers.get(registeredDrivers.indexOf(aDriver));
// If a DriverAction was specified, Call it to notify the
// driver that it has been deregistered
if(di.action() != null) {
di.action().deregister();
}
registeredDrivers.remove(aDriver);
} else {
// If the caller does not have permission to load the driver then
// throw a SecurityException.
throw new SecurityException();
}
} else {
println(" couldn't find driver to unload");
}
}
@CallerSensitive
public static java.util.Enumeration<Driver> getDrivers() {
java.util.Vector<Driver> result = new java.util.Vector<>();
Class<?> callerClass = Reflection.getCallerClass();
// Walk through the loaded registeredDrivers.
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerClass)) {
result.addElement(aDriver.driver);
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
return (result.elements());
}
public static void setLoginTimeout(int seconds) {
loginTimeout = seconds;
}
public static int getLoginTimeout() {
return (loginTimeout);
}
public static void println(String message) {
synchronized (logSync) {
if (logWriter != null) {
logWriter.println(message);
// automatic flushing is never enabled, so we must do it ourselves
logWriter.flush();
}
}
}
private static boolean isDriverAllowed(Driver driver, Class<?> caller) {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
return isDriverAllowed(driver, callerCL);
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver != null) {
Class<?> aClass = null;
try {
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception ex) {
result = false;
}
result = ( aClass == driver.getClass() ) ? true : false;
}
return result;
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
// 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.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
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());
}
}
// if we got here nobody could connect.
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");
}
}
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;
}
}
在注册之前,将传过来的Driver对象,封装成了一个DriverInfo对象。接下来继续执行客户端代码的第二步,调用DriverManager的getConnection()方法获取连接对象。在getConnection()中就又会调用各自厂商实现的Driver的connect()方法获得连接对象。这样的话,就巧妙的避开了使用继承,为不同的数据库提供了相同的接口。JDBC API中DriverManager就是桥,如下图所示:
五、桥接模式的优缺点
优点:
- 分离抽象部分及其具体实现部分。
- 提供了系统的扩展性。
- 符合开闭原则。
- 符合合成复用原则。
缺点:
- 增加了系统的理解与设计难度。
- 需要正确的识别系统中两个独立变化的维度。
六、友情链接
欢迎大家关注微信公众号(MarkZoe)互相学习、互相交流。