书接上回,本篇讲一下结构型模式-桥接设计模式
定义:将抽象部分与它的具体实现部分分离,使他们都可以独立的变化
实现:通过组合的方式建立两个类之间联系,而不是继承
这里得解释什么事抽象部分,什么事具体实现部分,什么是独立变化,还是什么事桥接
抽象部分
在Java中一般说到抽象,首先想到的是接口,抽象类,然后对应的抽象方法。这些都算抽象,但这理解停留在编码层面上,有点狭义。如果把抽象立意提升到架构设计上,抽象更多表示规则(规范)、约定(约束)。举个例子:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
//-------------------------------------
public class Thread implements Runnable {
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
public synchronized void start() {
strart0();
}
private native void start0();
public void run() {
if (target != null) {
target.run();
}
}
}
上面的JDK中线程Runnable接口,定义了一个run方法,约定线程执行要调用run方法,居于这个约定,Thread线程操作设计出以下规则:
1>实现Runable接口,重写run方法 2> 创建Thread实例,传入runable接口对象 3>start启动
有了这个基础之后,桥接模式中抽象部分指的是:业务逻辑(或说执行流程)的抽象,抽象部分定义操作逻辑规范,业务正式执行,依赖实现部分对象来实现。
实现部分
这个跟抽象部分是一对的,抽象是在定义规则,实现部分就是实现规则,还是以线程为例子:
抽象部分定义线程执行规则:
1>实现Runable接口,重写run方法 2> 创建Thread实例,传入runable接口对象 3>start启动
实现部分依照规则实现逻辑
//匿名内部类实现Runnable接口,重写run方法
Runable run = new Runnable() {
@Override
public void run() {
System.out.println(".....");
}
};
//创建线程对象,传入runable实例
Thread th = new Thread(run);
//启动线程,调用run对象中run方法
th.start();
独立变化
独立变化指的是抽象部分跟实现部分的变化,这里的变化个人认为是扩展,
抽象部分扩展:
根据业务/需求不同,再原有的基础上做扩展(比如扩展新的业务方法)
落地实现方法:接口继承,子类继承等
实现部扩展:跟上面一样,可以多接口实现,多子类实现
桥接
桥接模式还有一个核心点:桥接。何为桥接?就是建立抽象部分与实现部分连接通道,让他们可以进行通讯,实现实现方式:组合,更装逼的讲法:面向接口/抽象编程。比如:
public class EmployeeController {
private IEmployeeService employeeService = new EmployeeServiceImpl();
}
最后来看下,UML的定义。
UML
Abstratction:抽象部分的接口(或抽象类),定制具体实现规则,同时里面要持有实现部分对象(implementor)引用。
RefinedAbstraction:扩展抽象部分的实现,实现Abstratction接口,可以根据业务变化扩展出RefinedAbstractionA/B/C来。但是,不管怎么变,真实业务操作还是要靠实现部分对象(implementor)去实现。
implementor:定义实现部分的接口,定义的业务真实实现方法。
ConCreteImplementor:实现implementor,业务方法真实实现者,可以根据业务变化,扩展出ConCreteImplementorA/B/C来。
实现部分-Implementor
/**
* 具体部分
*/
public interface Implementor {
//真实要执行的逻辑
void operation();
}
实现部分-ConCreteImplementorA
//真实执行方式A
public class ConCreteImplementorA implements Implementor{
@Override
public void operation() {
System.out.println("具体部分A实现....");
}
}
实现部分-ConCreteImplementorB
//真实执行方式B
public class ConCreteImplementorB implements Implementor{
@Override
public void operation() {
System.out.println("具体部分B实现....");
}
}
抽象部分-Abstraaction
/**
* 抽象部分
* 定义操作规则,具体实现由Implementor实现
*/
public abstract class Abstraction {
private Implementor implementor;
public Abstraaction(Implementor implementor) {
this.implementor = implementor;
}
//业务操作规则
protected void operation(){
implementor.operation();
}
}
抽象部分-RefindedAbastractionA
//根据业务不一样,可以扩展出更多的具体实现调用规则
//这里是规则A
public class RefindedAbastractionA extends Abstraction{
public RefindedAbastractionA(Implementor implementor) {
super(implementor);
}
@Override
public void operation() {
System.out.println("调用xxx方法执行规则A")
super.operation();
}
}
抽象部分-RefindedAbastractionB
//根据业务不一样,可以扩展出更多的具体实现调用规则
//这里是规则B
public class RefindedAbastractionB extends Abstraction{
public RefindedAbastractionB(Implementor implementor) {
super(implementor);
}
@Override
public void operation() {
System.out.println("调用xxx方法执行规则B")
super.operation();
}
}
左边是抽象部分Abstraction,只有操作规则的定制(operation),没有具体实现,具体的实现依赖右边的Implementor 接口。
抽象部分跟具体部分使用组合模式建立操作通道,也就是桥接
private Implementor implementor;
public Abstraaction(Implementor implementor) {
this.implementor = implementor;
}
后续如果需求发送变化,抽象部分可以扩展出:RefindedAbastractionB 具体部分可以拓展出:ConCreteImplementorC。
需求:支付宝支付与微信支付
支付宝支付:余额,信用卡,银行卡
微信支付:余额,信用卡,银行卡
分析;
支付方式有3种:余额,信用卡,银行卡
支付平台有2种:支付宝,微信
能组合出来的支付逻辑:3 * 2 = 6种
支付宝余额支付,支付宝信用卡支付,支付宝银行支付
微信余额支付,微信信用卡支付,微信银行支付
这种场景就非常符合桥接模式啦:
1>不同维度(平台/支付方式)组合
2>抽象与实现分离--抽象:平台,平台设计支付逻辑。实现:支付方式,执行具体支付操作。
直接上代码:
UML
实现部分-IPay
//具体部分-支付逻辑
public interface IPay {
//支付
void pay(int money);
}
实现部分-BalancePay
//实现部分-余额支付
public class BalancePay implements IPay{
@Override
public void pay(int money) {
System.out.println("支付方式:余额,支付金额:" + money);
}
}
实现部分-BankCardPay
//实现部分-银行卡支付
public class BankCardPay implements IPay{
@Override
public void pay(int money) {
System.out.println("支付方式:银行卡,支付金额:" + money);
}
}
实现部分-CreditCardPay
//实现部分-信用卡支付
public class CreditCardPay implements IPay{
@Override
public void pay(int money) {
System.out.println("支付方式:信用卡,支付金额:" + money);
}
}
抽象部分-PayPlatform
//抽象部分-支付平台
public abstract class PayPlatform {
private IPay pay;
public PayPlaform(IPay pay) {
this.pay = pay;
}
public void pay(int money){
pay.pay(money);
}
}
抽象部分-AlipayPlatform
//抽象部分-支付宝平台
public class AlipayPlatform extends PayPlatform{
public AlipayPlatform(IPay pay) {
super(pay);
}
@Override
public void pay(int money) {
super.pay(money);
System.out.println("支付宝支付完成,赠送花呗1000额度...");
}
}
抽象部分-WeChatPlatform
//抽象部分-微信平台
public class WeChatPlatform extends PayPlatform{
public WeChatPlatform(IPay pay) {
super(pay);
}
@Override
public void pay(int money) {
super.pay(money);
System.out.println("微信支付完成,赠送微粒贷1000额度...");
}
}
测试
public class App {
public static void main(String[] args) {
//支付宝平台
new AlipayPlatform(new BalancePay()).pay(1000); //余额
new AlipayPlatform(new BankCardPay()).pay(1000); //银行卡
new AlipayPlatform(new CreditCardPay()).pay(1000); //信用卡
System.out.println("-------------------------------------------");
//微信平台
new WeChatPlatform(new BalancePay()).pay(1000); //余额
new WeChatPlatform(new BankCardPay()).pay(1000); //银行卡
new WeChatPlatform(new CreditCardPay()).pay(1000); //信用卡
}
}
结果
支付方式:余额,支付金额:1000
支付宝支付完成,赠送花呗1000额度...
支付方式:银行卡,支付金额:1000
支付宝支付完成,赠送花呗1000额度...
支付方式:信用卡,支付金额:1000
支付宝支付完成,赠送花呗1000额度...
-------------------------------------------
支付方式:余额,支付金额:1000
微信支付完成,赠送微粒贷1000额度...
支付方式:银行卡,支付金额:1000
微信支付完成,赠送微粒贷1000额度...
支付方式:信用卡,支付金额:1000
微信支付完成,赠送微粒贷1000额度...
解析
分析代码结构:Platform 负责制定平台整套支付流程,具体的实现交给IPay实现,标准的桥接模式。这么好处在于从容扩展,
比如:
加入银行的云支付,只需要再弄一个云支付Platform即可
加入积分支付,只需要再实现IPay接口即可。
完全符合开闭原则:扩展开放,修改闭合。
一套逻辑存在两个(或多个)独立变化的维度,且这两个(或多个)维度都需要独立进行扩展时,可用
不希望使用继承,或因为多层继承导致系统类个数剧增时,可用。
分离抽象部分及其具体实现部分,提高系统的可扩展性
符合开闭
符合合成复用
增加系统理解与设计难度
需要正确的识别系统2个独立变化的维度
来做一道题:用一句大白话讲清楚Java 面向接口编程。
大白话:接口把具体的实现和使用接口的客户端分离开来,从而使得具体的实现和使用接口的客户端可以分别扩展,而不会相互影响。
public class EmployeeServiceImpl implements IEmployeeService{
//private IEmployeeDAO dao= new EmployeeMysqlDAO();
private IEmployeeDAO dao= new EmployeeOracelDAO();
@Override
public void save() {
dao.save(....);
}
}
EmployeeServiceImpl 客户端依赖IEmployeeDAO接口,具体是mysql 的DAO实现还是Oracle的DAO实现,都无所谓,能执行即可。
仔细想想,是不是跟桥接模式操作优点类似呢?答案是yes的,从某种角度来说,桥接模式可以认为是面向接口/抽象编程的扩展。这种简化版的桥接模式,就是广义的桥接模式。
桥接模式在JDK中经典案例-JDBC
回顾一下JDBC操作步骤:贾琏欲执事
//贾:加载驱动
Class.forName("com.mysql.jdbc.Driver");
//琏:获取数据库连接
Connection connection = DriverManager.
getConnection("jdbc:mysql://localhost:3306/数据名", "数据库账号", "数据库密码");
//欲:获取预编译对象
PreparedStatement ps = connection.prepareStatement(sql);
//执:执行SQL语句
ps.executeUpdate();
//事:释放资源
ps.close();
connection.close();
从JDBC API上看,大家有没有发现,都是JDK的api,操作的都是接口,具体实现没有,到这反应过来了没,上面代码都是桥接模式中的抽象部分。那实现部分呢?就是各种数据库驱动程序啦。关系图大概是这样:
其中的DriverManager就是连接的桥梁
//com.mysql.jdbc.Driver
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!");
}
}
}
DriverManager 类中registeredDrivers 属性存放所有加载的jdbc驱动,算是建立桥接啦
public class DriverManager {
// List of registered JDBC drivers
private final static CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>();
public static void registerDriver(java.sql.Driver driver)
throws SQLException {
registerDriver(driver, null);
}
public static void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
}
}
桥接模式的本质:分离抽象和实现,适合多维度扩展
广义桥接模式:面向接口/抽象编程