桥接模式Bridge
Bridge 意为桥梁,桥接模式的做用就像桥梁同样,用于把两件事物链接起来
意图
将抽象部分与他的实现部分进行分离,使得他们均可以独立的发展。
意图解析
依赖倒置原则要求程序要依赖于抽象接口,不要依赖于具体实现。
简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就下降了客户与实现模块间的耦合
抽象
抽象就是将多个事物、实体中共同的概念提取出来
好比,一组共同特性的对象概念,能够提取出来类
若是一些类又具备共同的概念性联系,又能够提取出来抽象类和接口
实现
抽象的具体,就是实现
好比一个对象是一个类的实现,一个具体的子类是抽象父类的实现
类的功能层次结构
按照依赖倒置原则,咱们面向抽象进行编程
一般会使用接口或者抽象类用于描述功能概念
而后经过继承进行功能概念的扩展,子类继承父类,而且扩展父类以增长新的功能
类的实现层次结构
在基于功能层次结构的基础上,须要对方法接口或者概念等进行具体实现
这些全部的实现就组成了类的实现层次结构
好比:
定义一个图片编辑器imageEditor(接口)
分为windows和Linux两个平台(接口)
而后又分别有两个实现类windowsImpl 以及LinuxImpl(实现类)
图中,红框部分即为类的功能层次结构,蓝色矩形框部分即为类的实现层次结构
对于类的层级结构不是很复杂的时候,咱们可能常常会将类的功能层次结构和实现层次结构掺杂在一块儿
好比,你可能定义了一个抽象类A,而后他有两个子类B和C,B是用于实现,另外一个C倒是功能逻辑的扩展
当层次接口相对比较简单的时候,掺杂在一块儿,还相对容易应对
当层次结构变得很复杂时,若是仍是掺杂在一块儿将会变得很是难以处理
由于当你想要扩展产品功能逻辑时,你可能很难肯定到底应该在类的哪个层次结构中去扩展
咱们将编辑器抽象提取出来Editor(接口)
又有图形编辑器ImageEditor(接口)文本编辑器TextEditor(接口)视频编辑器VideoEditor(接口)
又分别有windows和linux两个版本的软件
红色框内为功能层次结构,蓝色框内为实现层次接口
上图就是经过继承的形式进行功能扩展与实现
能够看得出来,若是新增长一种新的编辑器,好比音频AudioEditor
那么可能须要新增长AudioWindowsEditor和AudioLinuxEditor以及他们的实现类AudioWindowsEditorImpl和AudioLinuxEditorImpl
除了新增的编辑器外,新增文件个数为4
若是,当新增长一种操做系统,好比 Os X
那么,将须要根据现有的Editor类型 建立对应的三个Os X操做系统的接口(ImgXEditor,TextXEditor,VideoXEditor)
而后在建立与之对应的三个实现类(ImgXEditorImpl,TextXEditorImpl,VideoXEditorImpl)
除了新增的操做系统外,新增文件个数为6
这种采用多层继承结构的形式,类的个数巨大
由于不只仅有多种类型的Editor,设类型个数为X
又须要在多个操做系统平台上进行实现,设平台个数为Y
实现类的个数为X*Y
|
扩展时,如上例,个数又将会爆发式的增加
随之而来的就是维护、使用、运行等成本的增长
|
继承从一开始就把抽象角色和实现角色进行了绑定,是一种强关联关系,并不符合
组合复用原则
编译时期就已经肯定,
不可以在运行时期进行变更
并且,继承将父类暴露给子类,若是父类发生变化,子类势必将会受到波及影响,将不得不作出修改
不符合开闭原则
再有就是,对于每个实现类,他即涉及具体类型的Editor又涉及平台,好比ImgWindowsEditor
用于处理图像img 又涉及到windows平台,那么涉及到img或者windows的修改,均可能会影响他致使修改
不符合单一职责原则
面对复杂继承层次结构带来的问题,因此,人们但愿可以
将抽象部分与他的实现部分进行分离,使得他们均可以独立的发展。
这就是桥接模式的最初动机
向分离的演进
仔细观察能够发现,之因此类会如此膨胀,扩展如此困难的缘由就在于他不止一个维度
Editor类型以及不一样平台实现两个维度
也正是这两个维度致使了不符合单一职责原则
是否能够将这两个维度进行分离?
若是可以分离,也就意味着不会是彻底使用继承层次结构
由于继承是强关联,彻底继承,就不是分离
那么,就须要考虑类的组合
|
若是可以分离,将他们拆分为各自不一样的维度,那么就不会出现类的个数爆炸式增加的状况
由于一旦分离,就能够各自独立发展
独立发展,那么增长一个Editor类型就是一个Editor类型
增长一个操做系统平台,就只是增长一个操做系统平台类型
不在爆炸增加
|
若是可以进行分离,那么他们各自负责本身不一样的维度,职责将会更加单一 |
客户端关注什么?
客户端程序的目的在于使用Editor,也就是Img、Text、Video的编辑
他其实并不在乎究竟是什么平台
并且,他也不该该在乎,只有这样才可以跨平台
可是,在咱们的类层次结构中,却恰恰的与平台创建了强关联
因此一种解决方案就是:
客户端面向Editor进行编程
Editor不关注平台的细节,将与平台相关的实现剥离开来
而平台的实现部分,经过组合的方式,组合到Editor中来
|
相似适配器模式(对象适配器模式),Editor做为目标对象Target
而与平台相关联的实现就是被适配的角色Adaptee
而每个类型好比ImgEditor都是Adapter
关于平台相关的细节部分,经过组合的方式
以下图所示Editor中含有指向Implementor的引用
涉及平台相关性的处理,借助于Implementor来完成
代码示例
package bridge;
/**
* 编辑器的抽象类
* 内部包含implementor 对Editor的请求能够借助于Implementor
*/
public abstract class Editor {
protected Implementor implementor;
public void setImplementor(Implementor implementor) {
this.implementor = implementor;
}
void doEdit(){
implementor.systemEditor();
}
}
package bridge;
public class ImgEditor extends Editor {
@Override
void doEdit() {
System.out.println("ImgEditor working");
implementor.systemEditor();
}
}
package bridge;
public class TextEditor extends Editor {
@Override
void doEdit() {
System.out.println("TextEditor working");
implementor.systemEditor();
}
}
package bridge;
public class VideoEditor extends Editor {
@Override
void doEdit() {
System.out.println("VideoEditor working");
implementor.systemEditor();
}
}
以上代码为上图中的左半部分
package bridge;
public interface Implementor {
void systemEditor();
}
package bridge;
public class WindowsImpl implements Implementor {
@Override
public void systemEditor() {
System.out.println("working on windows platform");
}
}
package bridge;
public class LinuxImpl implements Implementor {
@Override
public void systemEditor() {
System.out.println("working on linux platform");
}
}
测试代码
package bridge;
public class Test {
public static void main(String[] args) {
Editor editor = new ImgEditor();
editor.setImplementor(new WindowsImpl());
editor.doEdit();
}
}
在示例代码中,建立了一个ImgEditor
而后动态的借助于new WindowsImpl() 在windows平台上执行编辑任务
在真正的借助于平台底层,执行平台相关的代码以前,还进行了一些其余的处理
好比上面的打印语句:
System.out.println("ImgEditor working");
这种使用方式,用户关注的是Editor,不在与具体的平台进行绑定
具体的平台经过组合的形式组合到Editor中
在具体的使用到平台相关的方法中,Editor依赖于内部的Implementor 进行处理
扩展时,也不会出现类文件个数爆炸增加的问题
能够相互独立发展,这就是桥接模式
结构
抽象化角色Abstraction
抽象化给出定义,并保存一个对实现的引用
修正抽象化RefinedAbstraction
扩展抽象化角色,调整父类对抽象化的定义
实现角色Implementor
给出实现化角色的接口定义,但不给出具体的实现
这个接口不必定和Abstraction中的接口定义相同,实际上,能够彻底不相同也不要紧
实现化角色仅仅给出底层操做抽象化角色给出基于底层操做,更高一层的抽象操做
好比底层是基于平台的,更高一层则是ImgEditor这种
具体实现化角色ConcreteImplementor
给出实现化角色的具体实现代码
桥接模式的重点在于理解抽象化与实现化的概念含义
不要局限在java中定义一个接口A,而后定义一个实现类AImpl,而后override全部的方法
这种思惟方式太狭隘了
抽象化在于针对于底层操做的更高一层抽象,更高一层的调用
就像上面的例子,ImgEditor真正的实现须要依赖底层具体的平台,因此ImgEditor的doEdit方法是底层平台实现的抽象化
千万不要把抽象与实现局限在extends 和implements关键字的那种形式
应该认为,但凡是更高一层的调用,或者封装,均可以认为是一种抽象与实现
也正是由于这种模式是extends 和implements关键字场景的进一步抽象
因此也被称之为
接口Interface模式
extends 和implements关键字的形式天然是抽象与实现
一个对象中的方法,借助于另外的对象来实现,这也是必定程度上的“抽象化--实现化”
因此说适配器模式也是必定程度上的抽象化--实现化”
抽象化对象就像是手柄同样,经过手柄来操纵委派给实现化角色,因此桥梁模式也被称之为柄体模式 Handle and Body
抽象化等级结构中的方法,经过向对应的实现化对象委派实现本身的功能
这就意味着抽象化角色能够经过向不一样的实现化对象委派,
就能够达到动态的转换本身的功能的目的
在上面的示例中,
咱们使用
public void setImplementor(Implementor implementor)
进行Implementor的设置
通常常用工厂模式(方法)进行建立赋值
桥梁模式与JDBC
jdbc的百度百科
JDBC是桥梁模式的典型应用
他将抽象化与实现化进行分离
它为全部的关系数据库提供了一个通用的访问界面
提供了一组与具体厂家实现彻底无关的接口
有了JDBC这一组通用接口
应用系统就能够不依赖数据库引擎的具体细节而独立的演化
一个应用系统动态的选择一个合适的驱动器,而后经过驱动器向数据库引擎发出指令
这就是抽象角色的行为委派给实现角色来完成任务
厂家的实现与JDBC之间,并无任何的静态强关联
其实不少其余形式的驱动又未尝不是如此?
好比Office办公软件打印不须要关注于具体的打印机厂家型号
会有统一的驱动为咱们进行处理
模式对比
与适配器区别
上面说到桥接模式相似适配器模式
并且,某种程度上讲,适配器模式也符合
“抽象化---实现化”的概念
并且,从上面的结构图中,能够看得出来,桥接模式与对象适配器模式的类似程度
他们都拥有抽象的概念角色 Abstraction和Target
它们都拥有具体的客户端须要直接面对的角色 RefinedAbstraction和Adapter
他们都有工做须要委托给内部的“
工做人员
”Implementor 和 Adaptee
他们都依赖于“抽象”与“实现”的概念
那么到底有什么区别呢?
适配器模式的主要目的是让由于接口不兼容而不能互相工做的类可以一块儿工做
换句话说就是他们自己不一样,我用“纽带” Adapter将他们链接起来
而桥接模式则是将本来或许紧密结合在一块儿的抽象与实现,进行分离
使她们可以各自独立的发展,是把链接在一块儿的两个事物,拆分开来
而后用“纽带”“桥梁”(也就是对象的引用)将他们链接起来
适配器模式就比如
张三和王五不认识,李四介绍他们认识
桥梁模式比如张三和王五整天黏在一块儿活干得很差太乱套,李四说之后我做为接口人,你俩各干各的吧
虽然看起来都是两我的,中间一个联系人,可是含义倒是彻底不一样
与装饰器区别
装饰器模式中,使用组合而不是继承来对类的功能进行扩展,避免了类的个数的爆炸增加,与桥梁模式的结果不约而同
都解决了类爆炸增加的问题,都避免了过多的不必的子类
装饰器模式侧重于功能的动态增长,将额外的功能提取到子类中
经过不一样的排列组合,造成一个递归的调用方式,以动态的增长各部分的功能
桥梁模式是将本来系统中的实现细节抽取出来,好比原来抽象概念与实例化所有都是一个类层次结构中
把全部的实现细节,好比示例中的平台相关实现,抽取出来,进行分离
达到抽象与实现分离的目的
因此虽然他们均可以解决子类爆炸式增加、不易扩展的问题
可是他们的出发点彻底不一样
一个关注于功能的动态扩展组合
一个关注于抽象与实现的分离,得到更多的灵活性
总结
场景
若是一个系统要求抽象化角色和具体化角色之间增长更多的灵活性
避免在两个层次之间创建静态的联系
也就是实现化角色的改变,不会影响客户端,彻底透明的
若是系统须要在多个抽象化角色和实现化角色之间进行动态的耦合
也就是要求可以动态的组合
若是使用多层继承结构进行处理时,就能够考虑使用桥接模式
尤为是一个类存在两个或者多个变化的维度,并且这两个维度也可能须要动态的扩展
总之,当你须要抽象化和是实现化进行解耦时,你就能够考虑桥梁模式
解耦就会变得透明不会互相影响可以独立发展,解耦就可以动态的组合,解耦了就不会有静态的联系
优势
提升了扩展性,多层继承的良好替代方案
将抽象与实现进行解耦,更加符合单一职责原则 组合复用原则以及开闭原则
注意点
想要使用桥接模式,必然要理清楚你面对的需求中抽象与实现的部分,才能更好的进行运用。
只要具有抽象与实现分离的相关须要,均可以考虑桥梁模式
前面描述的多个维度不一样发展,多层次的抽象,是桥梁模式的更高级别的运用,
必需要先分析清楚变化的维度
并且最重要的就是分析出整个类层次结构中的“抽象化”部分和“实现化”部分
咱们的Editor示例中,ImgEditor TextEditor才是客户端程序关注的,他们不但愿关注于具体平台
JDBC中,客户端程序关注的是数据库的查询操做行为,而不但愿关注数据库的细节
因此你必须找准到底谁是抽象化
桥接模式的场景理解起来略微有点费神
可是他的根本逻辑倒是很是简单,那就是抽象的概念功能与具体的实现进行分离
桥接模式是面向抽象编程--依赖倒置原则的具体体现
而且使用组合的形式,解决了多层继承结构中的一些问题