现在我们来学习桥接模式,这个包建在结构这个包下面,bridge,那桥接模式就是把抽象和实现,分离出来,
然后中间通过组合,来搭建他们之间的桥梁,那我们现在有一个场景,例如中国有很多银行,有中国农业银行,
还有工商银行,那这两个银行也比较好记,一个叫ABC Bank,还有叫ICBC Bank,我们就有自己的账号,那关于我们的
储蓄账号呢,分为定期账号,还有活期账号,定义账号可以存三个月,半年一年,银行也有一定的利息,而这种利息是比
活期账号利息高很多的,那我们现在抽象的来想一下,那这里面就分两大块,一块是银行,另外一块就是我们的账号,
首先我们来创建一个类,这个类是一个接口,为什么要用接口呢,创建完这个接口之后,我们还要具体写他的实现,这个接口
就是Account账号
一旦你看到这个图,一定会很清晰的,这些都打开,那我们一起来看一下这个图,我们一起来回顾一下,首先我们创建了
Account,这个是什么呢,他是这个桥的实现接口,左侧是实现层,右侧是抽象层,所以桥接模式最重要的是把桥的左侧
和桥的右侧,你要桥接的两侧,划分清楚,Account可以理解成,他就是这个桥的实现接口,而下边这两个Account,一个
是定期账号,一个是活期账号,他们两个就是具体的实现类,他们来实现Account接口,然后注意,第三步我们创建了Bank,
创建了一个抽象类Bank,用的是什么呢,使用了Account这个接口,这个就是桥接模式的核心,注意这个抽象类使用了Account
接口,通过什么方式呢,通过组合的方式,所以这条线右侧有一个菱形,一个Bank里面含有一个Account,那下边的ICBCBank,
还有ABCBank,他们就是具体继承了Bank,这个抽象类具体的类实现,那我们通过这个桥接模式,把实现部分Account,和抽象
部分Bank,进行一个桥接,那当然我们说的实现部分,是指Account的具体的接口实现类,而抽象部分,就是指Bank这个抽象类,
而中间的这个横线呢,也就是桥接模式的一个关键,连接两个继承体系的一个桥,当然我们这里面是组合,也有通过聚合方式
实现桥接的,如果我们不用这种方式,会怎么样呢,打个比方,我们首先写一个Bank,然后这两个银行爱存不存,还有ABCBank,
然后我们在ICBCBank下边,再创建两个账号,一个是SavingAccount,另外一个是DepositAccount,同理呢ABCBank下边,也会有
这两个账号,那这导致了一个什么问题,如果我们银行很多,还有账号的类型很多的话,这个子类就要爆炸了,下边会有无数个的
类,那前面我们也有说,桥接模式在一定程度上,可以避免子类过多,子类剧增,子类爆炸的情况,那如果我们用刚刚我们说的方式,
就是通过最简单的一个继承的方案,所以桥接模式通过组合的方式,通过复合复用原则,来达到抽象层,和实现层,他们两之间进行
分离,并且最重要的一点,他们两可以独立的发展,再创建一个银行,在这里面创建银行,继承Bank就可以,在这里面创建一个账号,
继承Account就可以,他们两之间就可以无限组合,那这个就桥接模式的精华所在,希望你们能够深入理解一下,在这个模式当中,
使用组合优于继承,这个效果是非常非常明显的,那他到底有多方便呢,很简单,我们来写一下Test
注意中间是一个桥梁,最大的特色是连接两个继承体现中间的那座桥,也就是我们看到的一比一的关系,
左边是抽象部分,右边是实现部分,抽象部分可以独立的扩展自己,实现部分也可以独立的扩展自己,可以加
无数个银行,而账号这边呢,可以加各种理财账号,各种各样的账号,至少不管他们之间如何排列组合,我们都不会
发生类爆炸的情况,刚才我们在前面也说了,如果用继承的方式来实现的话,会是什么样子,那希望通过我们的
coding实战,对桥接模式有更深刻的理解,还有千万不要忘记委托行为的小坑
package com.learn.design.pattern.structural.bridge;
/**
* 在这里有两个方法
*
*
* @author Leon.Sun
*
*/
public interface Account {
/**
* 一个呢是打开我们的账号
* 打开账号又分为打开哪个银行的账号呢
* 例如leon有工商银行的账号
* 还有中国农业银行的账号
* 所以这里也是一个抽象
* 那既然打开账号呢
* 就要返回账号
* openAccount这么一个方法
*
*
* @return
*/
Account openAccount();
/**
* 还有是查看我们账户的类型
* 是活期储蓄
* 还是定期储蓄
* 还有一个showAccountType
* 看一下这个账号的类型
* 就现在的场景而言
* 我们有中国农业银行
* 中国工商银行
* 这是两个银行
* 还有有活期账号
* 还有定期账号
* 这个呢是两个不同类型的账号
* 他们交叉一下
* 就会有四种组合
* 那一旦说我们的银行不断的扩展
* 账号类型也不断地扩展的话
* 那么对于桥接模式
* 就再适合不过了
* 他们两个都可以随着自己的层级
* 进行扩展
* 随着我们coding的时候呢
* 希望对这一块的理解呢
* 会加深
* 首先呢我们创建一个定期账号
* 来实现这个接口
* DepositAccount
*
*
*/
void showAccountType();
}
package com.learn.design.pattern.structural.bridge;
/**
* 他来实现Account
* 实现两个方法
* 这个账号是一个定期账号
* 我们再创建一个活期账号的类
* 新建一个类
* SavingAccount
* DepositAccount是定期存款
* 如果提前取出来的话需要交罚金的
*
*
*
* @author Leon.Sun
*
*/
public class DepositAccount implements Account {
/**
* 写一下这个类的实现
*
*
*/
@Override
public Account openAccount() {
/**
* 首先输出
* "打开定期账号"
*
*
*/
System.out.println("打开定期账号");
/**
* 返回值直接new一个
* 非常适合回来来看
* 写完之后你们先体会
* 回头来给大家讲
* 因为我几年前在学模式的时候
* 当时从上至下的来学习的时候
* 理解的并不是很透彻
* 但是一旦写完之后
* 再回头来看
* 一切都非常清晰明了
* 我们边写也会边讲
* 一起来体会
* 我们再回头重点来讲
* 那我们继续来coding
* 那现在就得写我们的银行类
* 创建一个类Bank
*
*
*/
return new DepositAccount();
}
@Override
public void showAccountType() {
/**
* 这个是一个定期账号
*
* 直接到这里
* 这是一个定期账号
* 那这个时候F8通过
*
*
*/
System.out.println("这是一个定期账号");
}
}
package com.learn.design.pattern.structural.bridge;
/**
* 他也实现Account接口
* 实现这两个方法
* SavingAccount是随时可以取钱的那种
*
*
* @author Leon.Sun
*
*/
public class SavingAccount implements Account {
/**
* 打开这个账号需要返回这个账号
*
*
*/
@Override
public Account openAccount() {
/**
* 所以我们输出一下打个标记
*
*
*/
System.out.println("打开活期账号");
//...
/**
* new一个SavingAccount
*
*
*/
return new SavingAccount();
}
@Override
public void showAccountType() {
/**
* 直接输出"这是一个活期账号"
*
*
*/
System.out.println("这是一个活期账号");
}
}
package com.learn.design.pattern.structural.bridge;
/**
* 这个银行类很简单
* 他要写成一个抽象类
* 为什么呢
* 因为现在我们要把Account
* 引入到Bank里边
* 然后通过这种组合的方式
* 交给子类来实现他的行为
* 而这个行为就是openAccount
* 至少我要确定
* 打开的是哪个银行的账号
* 那首先呢既然要交给子类
*
*
* @author Leon.Sun
*
*/
public abstract class Bank {
/**
* protected
* 声明一个account
* 子类能够拿到Account
* 然后我来写一下他的构造器
*
*
*/
protected Account account;
/**
* 构造器我可以把account拿过来
* 也可以通过set注入的方式
*
* 然后在父类上也打一个断点
*
* 所以就把这个account赋值给抽象类Bank
* 里面组合的Account
* F6单步
* 可以看到这个时候已经有值了
*
*
* @param account
*/
public Bank(Account account){
/**
* 然后赋值
* 把这些断点去掉
* 看一下结果
* 那在使用桥接模式的时候
* 有一个小坑
* 这个小坑是什么呢
* 例如我们现在打开中国工商银行账号
* 然后直接显示了这是一个定期账号
* 这个没有什么问题
* 但是我们要注意
* 我们委托之后
* 到底有没有使用他呢
* 我们看一下
*
*
*/
this.account = account;
}
/**
* 然后我们要写一个抽象方法
* 那这个方法是什么呢
* 正式Account接口的方法
* 那一会就知道
* 为什么我这个想抽象类里边
* 还要写一个抽象方法
* 而这个抽象方法
* 和接口里面声明的
* 抽象方法是一样的
* 那这里面我也要强调一下
* 声明成一样的方法名
* 这个并不是强制的
* 那我们声明成一样的方法名呢
* 是为了你们更方便的理解
* 因为Bank里面的具体的方法
* 要委托给Account的openAccount
* 这个方法
* 所以就把它们声明成一样的方法名
* 其实不叫一样的方法名也是OK的
* 只不过这种委托关系
* 在我们这个case里边
* 逻辑比较简单
* 并不会增强这个openAccount方法
* 所以就直接委托给他
* 他们两个的方法名是一样的
* 里边的实现所表达的含义也是一样的
* 然后这里边再说一下
* 看到的Account类
* 那在我们现在这个结构中
* Account就是具体的实现
* 因为我们要通过Account的接口实现
* Bank就是抽象
* 然后抽象类里面的某个行为
* 委托给Account这个接口
* 那前面我们也有说
* 桥接模式是指抽象和实现分离
* 那实现是什么呢
* 实现正式Account的两个实现类
* 那Account是有名词的含义
* 那还有动词
* 动词就是报账 查账
* 我们可以认为Account是一个行为接口
* 里面是两个动作
* 一个是open
* 一个是show
* 打开账号
* 还是查看账号类型呢
* 这些都是动作
* 那这里面还要再强调一次
* 抽象和实现分离
* 在桥接模式当中
* 抽象是指抽象类
* 因为还有各种扩展
* 而具体的实现呢
* 一般也都是行为
* 这里也说了
* 当然不排除特殊情况
* 这里面的account也是动词
* 那桥接模式当中的实现
* 就是Account的两个接口实现
* 而这个方法名字
* 并不强制一模一样
* 不过我们这里是强制委托
* 如果方法要达到的目的是一模一样的
* 所以就直接copy这个方法名了
* 这里面还是比较灵活的
* 并没有强制限制
* 那我们现在来创建中国农业银行
* ABCBank
*
*
* @return
*/
abstract Account openAccount();
}
package com.learn.design.pattern.structural.bridge;
/**
* 他继承Bank
* 这个抽象父类
*
*
* @author Leon.Sun
*
*/
public class ABCBank extends Bank {
/**
* 红线说没有构造器
* 我们构造一个
* 调用super
* 调用父类的构造器
*
*
* @param account
*/
public ABCBank(Account account) {
super(account);
}
/**
* 这里面实现就很简单了
* 构造的时候传入的什么Account
* 就返回什么Account
* 而这个Account正式父类的Account
* 所以这里面直接返回Account就可以了
*
*
*/
@Override
Account openAccount() {
/**
* 直接输出
* "打开中国农业银行账号"
* 同时我们建立一个爱存不存账号
* ICBCBank
*
*
*/
System.out.println("打开中国农业银行账号");
/**
* 然后调用了ICBCBank
* 很明显这个就是ICBC的银行
* 所以他肯定知道这个是中国工商银行
* 那具体是什么账号
* 他把它再进行一个返回
* 那这个返回之后
* 就来到了DepositAccount
*
* 在这里我们有时候写业务逻辑的时候
* 经常有忘记委托的这种情况
* 隐瞒了一个点
* 这个平时在使用桥接模式的时候
* 一定不要忘记
* 不要把具体的实现自己完成
* 而是要委托给Account来实现
* 否则创建相同名字的方法
* 也就没有意义了
* 因为只有通过具体的委托
* 以后account里边的openAccount方法
* 如果扩展的话
* Bank这里边是不需要动的
* 不要把这个实现移到openAccount里边
* 这里一定要注意
* 一定要把你具体的行为委托出去
* 委托给谁呢
* 正是委托给注入的Account
* 那我们再run一下
* 来看一下实际的结果
* 刚刚那个小坑也是故意埋下的一个伏笔
* 希望通过这坑
* 一定要印象深刻
* 我们再看一下结果
*
*
*/
account.openAccount();
return account;
}
}
package com.learn.design.pattern.structural.bridge;
/**
* 继承Bank这个类
*
* @author Leon.Sun
*
*/
public class ICBCBank extends Bank {
/**
* 然后构造器加上
*
*
* @param account
*/
public ICBCBank(Account account) {
/**
* 进入这里
*
* F6
* 现在来创建工商银行
* 并且把这个账号拿过来了
* 他呢又调用父类的构造器
*
*
*/
super(account);
}
@Override
Account openAccount() {
/**
* 打开的是工商银行账号
* 现在我们来看一下UML图
*
* 注意这里
* 我们打开中国工商银行账号之后
* 我们要把具体的行为委托给Account
*
*
*/
System.out.println("打开中国工商银行账号");
/**
* 调用它的openAccount
* 这样才是真正实现openAccount这个行为
* 委托给account
* 委托来执行
* 所以ABCBank也是一样的
*
*
*/
account.openAccount();
/**
* 直接return account
*
*/
return account;
}
}
package com.learn.design.pattern.structural.bridge;
/**
*
* @author Leon.Sun
*
*/
public class Test {
public static void main(String[] args) {
/**
* 我们创建一个银行
* 爱存不存银行
* ICBCBank
* 这里面放一个DepositAccount
*
* 首先我们进入他的构造器来看一下
*
*
*/
Bank icbcBank = new ICBCBank(new DepositAccount());
/**
* 然后从这里面拿一个账号
* icbcBank.openAccount
* 打开这个账号
*
* 这里打开了中国工商银行账号
*
* 同时打开这个银行之后
* 我又打开了定期账号
* 这是因为我们把定期账号
* 注入到ICBC账号里边
* 所以他打开账号的时候
* 首先打开中国工商银行账号
* 然后打开里面具体的的定期账号
*
*
*/
Account icbcAccount = icbcBank.openAccount();
/**
* 然后调用它的showAccountType方法
*
* 这里是一个定期账号
*
* 最后show一下AccountType
* 也就是这个
* 现在这个使用才是非常标准的使用方式
* 现在我们再结合前面的原则来说
* 如果我们不把具体的实现委托给Account
* 这个接口的话
* 那我们对ABC Account的实现
* 是非常可能不符合迪米特原则的
* 那我们回来再看一下
*
*/
icbcAccount.showAccountType();
/**
* 后面直接加一个2
* 那现在我们来run一下
* 我们来看一下结果
*
*
*
*/
Bank icbcBank2 = new ICBCBank(new SavingAccount());
/**
* 这里打开一个中国工商银行账号
*
*
*/
Account icbcAccount2 = icbcBank2.openAccount();
/**
* 这个是一个活期账号
*
*
*/
icbcAccount2.showAccountType();
/**
* 这个时候我再创建一个ABCBank
* 这里面就换一下了
* SavingAccount
* 这个时候即使我需要一个工商银行的SavingAccount
* 也是很容易的
* 我们直接copy一份出来
*
*
*
*/
Bank abcBank = new ABCBank(new SavingAccount());
/**
* 打开中国农业银行账号
*
*
*/
Account abcAccount = abcBank.openAccount();
/**
* 这是一个活期账号
* 那现在debug来跟一个
* 非常简单
* 我们直接run debug
*
*
*/
abcAccount.showAccountType();
}
}