将抽象部分与它的实现部分分离,使它们都可以独立地变化。
我戴着眼镜看了好几眼都没看明白,要是有读者一眼看懂了,欢迎留个联系方式不吝赐教。
实战业务场景此文中的代码引入了 lombok 依赖
首版一种理解方式:一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。通过组合关系来替代继承关系,避免继承层次的指数级爆炸。
系统给客户发通知,有电话和邮箱两种方式,咱们第一版代码可能是这样的:
public abstract class Notification {
abstract void send(String message);
}
@Slf4j
public class PhoneNotification extends Notification {
@Override
public void send(String message) {
log.info("使用 PhoneNotification 发送:{}", message);
}
}
@Slf4j
public class EmailNotification extends Notification {
@Override
public void send(String message) {
log.info("使用 EmailNotification 发送:{}", message);
}
}
迭代
后来加了个要根据系统异常等级(一般、紧急)发送指定方式的通知(邮件、电话),代码可能成下面的了:
@Slf4j
public class NormalEvent extends EmailNotification{
@Override
public void send(String message) {
log.info("一般情况:使用 EmailNotification 发送:{}", message);
}
}
@Slf4j
public class UrgencyEvent extends EmailNotification{
@Override
public void send(String message) {
log.info("紧急情况:使用 EmailNotification 发送:{}", message);
}
}
// 省略继承 PhoneNotification 相关类似代码
后来,需求又加了,不但加了个新的异常等级,还加了个新的通知方式。
下面要做的想想就头大:
重构想到要在以上代码的基础上新加一个短信通知的类,还得在之前的邮件通知、电话通知上各加一个新的异常等级「你不来以后你就不用来了」,还要在新添加的短信通知类上加上之前的异常等级(一般、紧急)以及新的「你不来以后你就不用来了」等级。
先定义个 Notification 接口:
public interface Notification {
void send(String message);
}
再来对应的实现类:
@Slf4j
public class EmailNotification implements Notification {
@Override
public void send(String message) {
log.info("使用 EmailNotification 发送:{}", message);
}
}
@Slf4j
public class PhoneNotification implements Notification {
@Override
public void send(String message) {
log.info("使用 PhoneNotification 发送:{}", message);
}
}
在来个事件的抽象类:
留一个博客的坑:合成复用原则。
public abstract class Event {
private Notification notification;
public Event() {
}
public Event(Notification notification) {
this.notification = notification;
}
void send(String message){
this.notification.send(message);
}
}
添加对应子类:
public class NormalEvent extends Event {
public NormalEvent(Notification notification) {
super(notification);
}
@Override
void send(String message) {
message = "NormalEvent -> " + message;
super.send(message);
}
}
public class UrgencyEvent extends Event {
public UrgencyEvent(Notification notification) {
super(notification);
}
@Override
void send(String message) {
message = "UrgencyEvent -> " + message;
super.send(message);
}
}
最后来测试一下:
public class BridgeTest {
public static void main(String[] args) {
// 一般消息用邮件通知
Notification emailNotification = new EmailNotification();
Event event = new NormalEvent(emailNotification);
event.send("message");
// 紧急消息用电话通知
Notification phoneNotification = new PhoneNotification();
Event urgencyEvent = new UrgencyEvent(phoneNotification);
urgencyEvent.send("message");
}
}
类图
这里的组合关系其实就是说的「桥」了,通过组合的方式(其实就是通过有参构造器)将不同事件和不同通知方式组合在一起,实现自己想要的功能。
如果后期加新的事件,只需要继承一个新的 Event 抽象类重写 send 方法即可,加新的通知方式亦是如此。不会出现像「迭代」部分中那样加一个新的需求要改好几个地方的情况。
源码场景在此例中:说的抽象并不是接口和抽象类,实现也不是实现类。抽象是 JDBC,实现是具体的 Driver(比如本例中提到的 com.mysql.jdbc.Driver)。抽象是指和具体数据库无关的、被抽象出来的类库;实现是指和数据库相关的类库。JDBC 和 Driver 独立开发,通过对象之间的组合关系,组装在一起。JDBC 的所有逻辑操作,最终都委托给 Driver 来执行。—— via 桥接模式
想必大家都看到过以下的代码:
// 加载驱动,反射获取驱动类
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc://mysql://host:port/database?user=xxx&password=xxx";
Connection con = DriverManager.getConnection(url);
Statement statement = con.createStatement();
String querySql = "select * from table_name";
ResultSet rs = statement.executeQuery(querySql);
来说下第一行代码做了什么:JVM 查找加载指定的类,在类加载的时候将 MySQL Driver 注册到 DriverManager 中,如下代码:
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!");
}
}
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
再来看下 DriverManager 代码,指定好的 Driver 注册到 DriverManager 之后再通过刚才指定的驱动获取连接:
public class DriverManager {
// List of registered JDBC drivers
private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>();
// ……
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class> caller) throws SQLException {
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");
}
}
分析
这点可以去看合成复用相关内容。
好处- 将抽象和具体的实现部分进行了分离
- 符合开闭原则
- 符合合成复用原则
- 方便后期扩展
使用的时候要建立在能看出来系统中两个独立变化的维度且二者有联系(在此例中就是一组通知方式的类和一组事件相关的类)。



