桥接模式(Bridge Pattern)是一种结构型设计模式,用于将抽象与其实现相分离,以便两者可以独立地进行变化,实现了抽象和具体实现的解耦。
我们通过 JDBC 驱动的例子来看一下这个设计模式,JDBC 驱动是桥接模式的经典应用。
获取一个连接如下所示:
// 1.数据库连接的4个基本要素:
String url = "jdbc:mysql://127.0.0.1:3306/mydb?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia
/Shanghai";
String user = "root";
String password = "root";
String driverName = "com.mysql.cj.jdbc.Driver";
// 2.实例化Driver,可省略
// 3.注册驱动,可省略
// 4.获取连接
conn = DriverManager.getConnection(url, user, password);
如果要把 MySQL 数据库换成 Oracle 数据库,只需要把驱动com.mysql.cj.jdbc.Driver 换成 oracle.jdbc.driver.OracleDriver ,同时将url进行替换就可以了。
当然在工作中会将上边的内容配置在文件当中,当发生改变的时候仅仅需要修改配置文件即可,完全不需要修改一行代码。
不管是改代码还是改配置,在项目中从一个数据库切换到另一种数据库,都只需要改动很少的代码,或者完全不需要改动代码,那如此优雅的数据库切换是如何实现的呢? 源码:
package com.mysql.cj.jdbc;
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”) 的时候,实际上是做了两件事情。
当我们把具体的 Driver 实现类(比如,com.mysql.cj.jdbc.Driver)注册到DriverManager 之后,后续所有对 JDBC 接口的调用,都会委派到对具体的 Driver实现类(com.mysql.cj.jdbc.Driver)来执行。而 Driver 实现类都实现了相同的接口(java.sql.Driver ),这也是可以灵活切换 Driver 的原因。
package java.sql;
public class DriverManager {
private DriverManager(){}
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
// 注册驱动到DriverManager
registerDriver(driver, null);
}
// 注册驱动到DriverManager
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 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) {
// 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");
}
}
桥接模式的定义是“将抽象和实现解耦,让它们可以独立变化。JDBC 本身就相当于“抽象”
,具体的 Driver(比如,com.mysql.cj.jdbc.Driver)就相当于“实现”
。JDBC 和 Driver 独立开发,通过对象之间的组合关系,组装在一起。JDBC 的所有逻
辑操作,最终都委托给具体的 Driver 来执行。
注意,这里所说的“抽象”,指的并非“抽象类”或“接口”,而是跟具体的数据库无关的、被抽象出来的一套“类库”。
注意,这里所说的“实现”,也并非指“接口的实现类”,而是跟具体数据库相关的一套“类库”。
通过将抽象的JDBC接口与具体数据库厂商的实现相分离,我们可以实现对抽象和实现的独立扩展。这种设计模式使得在不修改客户端代码的情况下更换数据库驱动程序成为可能,从而提高了代码的可维护性和可扩展性。
桥接模式的核心概念:
抽象化:在抽象类中定义抽象业务的接口和一个对实现层次结构的引用。抽象化的主要目的是隐藏实现的细节,以便可以在不影响客户端的情况下更改实现。(比如DriverManager中有Driver的引用,通过registerDriver赋值引用)
实现化:这是一个接口,定义了实现抽象化的方法。不同的具体实现类可以有不同的实现方式。(比如Driver#getConnection)
扩展抽象化:这是抽象化的一个具体实现,它定义了抽象业务的具体操作。(比如DriverManagerImpl#getConnection)
具体实现化:实现化接口的具体实现类。这些类为抽象业务提供具体的实现。(比如MysqlDriver)
优点:
缺点:
实现化接口代码:
/**
* 类描述:具体实现的抽象接口,与DriverManager抽象化进行分离,便于独立扩展
*
* @Author crysw
* @Version 1.0
* @Date 2023/12/19 21:38
*/
public interface Driver {
Connection connect();
}
实现化的具体实现
/**
* 类描述:具体实现
*
* @Author crysw
* @Version 1.0
* @Date 2023/12/19 21:40
*/
@Slf4j
public class MysqlDriver implements Driver {
@Override
public Connection connect() {
log.info(">>>MysqlDriver#connect");
return new Connection("Mysql");
}
}
@Slf4j
public class OracleDriver implements Driver {
@Override
public Connection connect() {
log.info(">>> OracleDriver#connect");
return new Connection("Oracle");
}
}
抽象化,依赖实现化接口的引用
/**
* 类描述:抽象化
*
* @Author crysw
* @Version 1.0
* @Date 2023/12/19 21:42
*/
public abstract class DriverManager {
// 依赖于抽象而不是具体实现
private Driver driver;
/**
* 注册驱动,实例化driver
*
* @param driver
*/
public void register(Driver driver) {
this.driver = driver;
}
/**
* 通过驱动driver获取连接
*
* @return
*/
public Connection getConnection() {
return driver.connect();
}
}
扩展抽象化
/**
* 类描述:扩展抽象化
*
* @Author crysw
* @Version 1.0
* @Date 2023/12/19 21:54
*/
@Slf4j
public class DriverManagerOne extends DriverManager {
@Override
public Connection getConnection() {
log.info("DriverManagerOne管理的连接");
return super.getConnection();
}
}
@Slf4j
public class DriverManagerTwo extends DriverManager {
@Override
public Connection getConnection() {
log.info("DriverManagerTwo管理的连接");
return super.getConnection();
}
}
测试案例
/**
* 类描述:桥接设计模式测试案例
*
* @Author crysw
* @Version 1.0
* @Date 2023/12/19 21:56
*/
@Slf4j
public class BridgePatternTest {
@Test
public void test() throws Exception {
// 先创建一个驱动
Driver driver = (Driver) Class.forName("cn.itcast.designPatterns.bridge.implementor.OracleDriver").newInstance();
// 注册给驱动管理类
DriverManager driverManager = new DriverManagerOne();
driverManager.register(driver);
// 获取连接
Connection connection = driverManager.getConnection();
log.info("connection>>>{}", connection);
driver = (Driver) Class.forName("cn.itcast.designPatterns.bridge.implementor.MysqlDriver").newInstance();
driverManager = new DriverManagerTwo();
driverManager.register(driver);
connection = driverManager.getConnection();
log.info("connection>>>{}", connection);
}
}
测试结果:
[main] INFO cn.itcast.designPatterns.bridge.abstractor.DriverManagerOne - DriverManagerOne管理的连接
[main] INFO cn.itcast.designPatterns.bridge.implementor.OracleDriver - >>> OracleDriver#connect
[main] INFO cn.itcast.designPattern.BridgePatternTest - connection>>>Connection(providerName=Oracle)
[main] INFO cn.itcast.designPatterns.bridge.abstractor.DriverManagerTwo - DriverManagerTwo管理的连接
[main] INFO cn.itcast.designPatterns.bridge.implementor.MysqlDriver - >>>MysqlDriver#connect
[main] INFO cn.itcast.designPattern.BridgePatternTest - connection>>>Connection(providerName=Mysql)
在这个例子中,我们有一个抽象化类DriverManager 和一个实现化接口Driver 。通过将抽象化类与实现化接口分离,在不影响客户端的情况下更改实现。比如可以为Driver添加新的具体实现类,同时在DriverManager的子类中使用这些新的实现。
在上述示例中,我们创建了两个具体实现化类OracleDriver 和MysqlDriver,它们分别实现了Driver接口。然后我们创建了一个扩展抽象化类DriverManagerOne,它扩展了抽象化类DriverManager 并实现了getConnection 方法。在测试案例中分别使用了这两个具体实现类来创建DriverManager 类的实例,并调用了它们的getConnection 方法。
桥接模式和装饰者模式在某些方面有一定的相似性,但它们解决的问题和目的是不同的。让我们来对比一下这两种设计模式:
相似性:
区别:
目的:装饰者模式主要用于在不修改原始类代码的情况下向对象添加新功能,而桥接模式主要用于将抽象与实现分离,以便两者可以独立地进行变化。
结构:装饰者模式中,装饰者类和原始类通常实现相同的接口;而桥接模式中,抽象类和实现类通常是分属不同的接口。
扩展性:装饰者模式可以动态地为对象添加新功能,而桥接模式则关注于抽象和实现的独立扩展。
系统需要支持多种通知方式(例如邮件、短信、即时消息等)以及多种通知紧急程度(普通、紧急、非常紧急等)。可以使用桥接模式将通知方式和通知紧急程度分离,使得它们可以独立地进行变化和扩展。
// 通知方式接口(实现化角色)
interface MessageSender {
void send(String message);
}
// 邮件通知实现类
class EmailSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("发送邮件通知: " + message);
}
}
// 短信通知实现类
class SmsSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("发送短信通知: " + message);
}
}
// 即时消息通知实现类
class InstantMessageSender implements MessageSender {
@Override
public void send(String message) {
System.out.println("发送即时消息通知: " + message);
}
}
// 抽象通知类(抽象化角色)
abstract class Notification {
protected MessageSender messageSender;
public Notification(MessageSender messageSender) {
this.messageSender = messageSender;
}
abstract void notify(String message);
}
class NormalNotification extends Notification {
// 依赖实现化接口的引用 MessageSender
public NormalNotification(MessageSender messageSender) {
super(messageSender);
}
@Override
void notify(String message) {
messageSender.send("普通:" + message);
}
}
// 紧急通知子类(扩展抽象化角色)
class UrgentNotification extends Notification {
public UrgentNotification(MessageSender messageSender) {
super(messageSender);
}
@Override
void notify(String message) {
messageSender.send("紧急:" + message);
}
}
// 非常紧急通知子类(扩展抽象化角色)
class CriticalNotification extends Notification {
public CriticalNotification(MessageSender messageSender) {
super(messageSender);
}
@Override
void notify(String message) {
messageSender.send("非常紧急:" + message);
}
}
// 测试案例
public class BridgePatternExample {
public static void main(String[] args) {
// 三种场景的具体实现
MessageSender emailSender = new EmailSender();
MessageSender smsSender = new SmsSender();
MessageSender instantMessageSender = new InstantMessageSender();
// 桥接的抽象化扩展使用
Notification normalEmailNotification = new NormalNotification(emailSender);
normalEmailNotification.notify("有一个新的任务待处理。");
Notification urgentSmsNotification = new UrgentNotification(smsSender);
urgentSmsNotification.notify("系统出现故障,请尽快处理!");
Notification criticalInstantMessageNotification = new CriticalNotification(instantMessageSender);
criticalInstantMessageNotification.notify("系统崩溃,请立即处理!");
}
}
在这个示例中,使用桥接模式将通知方式( MessageSender 接口及其实现类)和通知紧急程度( Notification 类及其子类)分离。这使得我们可以独立地添加更多的通知方式和通知紧急程度,而不会导致类的数量爆炸性增长。
电商项目中需要处理多种支付方式
(如信用卡、PayPal、支付宝等)和多种折扣策略
(如VIP折扣、新用户折扣、优惠券等)。
可以使用桥接模式将支付方式
和折扣策略
分离,使得它们可以独立地进行变化和扩展。
支付方式的抽离:
// 支付方式实现化接口
interface PaymentMethod {
void pay(double amount);
}
// 信用卡支付实现类
class CreditCardPayment implements PaymentMethod {
@Override
public void pay(double amount) {
System.out.println("使用信用卡支付: " + amount);
}
}
// PayPal支付实现类
class PayPalPayment implements PaymentMethod {
@Override
public void pay(double amount) {
System.out.println("使用PayPal支付: " + amount);
}
}
// 支付宝支付实现类
class AlipayPayment implements PaymentMethod {
@Override
public void pay(double amount) {
System.out.println("使用支付宝支付: " + amount);
}
}
折扣策略的抽象化及扩展增强:
// 折扣策略接口(抽象化角色)
abstract class DiscountStrategy {
protected PaymentMethod paymentMethod;
public DiscountStrategy(PaymentMethod paymentMethod) {
this.paymentMethod = paymentMethod;
}
abstract double getDiscountedAmount(double originalAmount);
public void payWithDiscount(double originalAmount) {
double discountedAmount = getDiscountedAmount(originalAmount);
paymentMethod.pay(discountedAmount);
}
}
// VIP折扣策略子类(扩展抽象化角色)
class VipDiscountStrategy extends DiscountStrategy {
public VipDiscountStrategy(PaymentMethod paymentMethod) {
super(paymentMethod);
}
@Override
double getDiscountedAmount(double originalAmount) {
return originalAmount * 0.9; // VIP用户享有9折优惠
}
}
// 新用户折扣策略子类(扩展抽象化角色)
class NewUserDiscountStrategy extends DiscountStrategy {
public NewUserDiscountStrategy(PaymentMethod paymentMethod) {
super(paymentMethod);
}
@Override
double getDiscountedAmount(double originalAmount) {
return originalAmount * 0.95; // 新用户享有95折优惠
}
}
测试用例:
public class ECommerceExample {
public static void main(String[] args) {
// 不同支付方式
PaymentMethod creditCardPayment = new CreditCardPayment();
PaymentMethod payPalPayment = new PayPalPayment();
PaymentMethod alipayPayment = new AlipayPayment();
// 折扣策略的抽象化扩展
DiscountStrategy vipCreditCardStrategy = new VipDiscountStrategy(creditCardPayment);
vipCreditCardStrategy.payWithDiscount(100);
DiscountStrategy newUserPayPalStrategy = new NewUserDiscountStrategy(payPalPayment);
newUserPayPalStrategy.payWithDiscount(100);
}
}
这个示例使用桥接模式将支付方式
( PaymentMethod 接口及其实现类)和折扣策略
( DiscountStrategy 类及其子类)分离。这样可以独立地添加更多的支付方式和折扣策略,而不会导致类的数量爆炸性增长。