桥接模式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中,客户端程序关注的是数据库的查询操作行为,而不希望关注数据库的细节
所以你必须找准到底谁是抽象化
桥接模式的场景理解起来略微有点费神
但是他的根本逻辑却是非常简单,那就是抽象的概念功能与具体的实现进行分离
桥接模式是面向抽象编程--依赖倒置原则的具体体现
并且使用组合的形式,解决了多层继承结构中的一些问题

桥接模式 桥梁模式 bridge 结构型 设计模式(十二)的更多相关文章

  1. 装饰器模式 Decorator 结构型 设计模式 (十)

    引子           现实世界的装饰器模式 大家应该都吃过手抓饼,本文装饰器模式以手抓饼为模型展开简介 "老板,来一个手抓饼,  加个培根,  加个鸡蛋,多少钱?" 这句话会不 ...

  2. OOAD-设计模式(四)结构型模式之适配器、装饰器、代理模式

    前言 前面我们学习了创建型设计模式,其中有5中,个人感觉比较重要的是工厂方法模式.单例模式.原型模式.接下来我将分享的是结构型模式! 一.适配器模式 1.1.适配器模式概述 适配器模式(Adapter ...

  3. 享元模式 FlyWeight 结构型 设计模式(十五)

    享元模式(FlyWeight)  “享”取“共享”之意,“元”取“单元”之意. 意图 运用共享技术,有效的支持大量细粒度的对象. 意图解析 面向对象的程序设计中,一切皆是对象,这也就意味着系统的运行将 ...

  4. 代理模式 PROXY Surrogate 结构型 设计模式(十四)

    代理模式 PROXY 别名Surrogate 意图 为其他的对象提供一种代理以控制对这个对象的访问. 代理模式含义比较清晰,就是中间人,中介公司,经纪人... 在计算机程序中,代理就表示一个客户端不想 ...

  5. 组合模式 合成模式 COMPOSITE 结构型 设计模式(十一)

    组合模式(合成模式 COMPOSITE) 意图 将对象组合成树形结构以表示“部分-整体”的层次结构. Composite使得用户对单个对象和组合对象的使用具有一致性.   树形结构介绍 为了便于理解, ...

  6. 设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型)

     设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型) 1.概述 你去政府部门求人办事过吗?有时候你会遇到过官员踢球推责,你的问题在我这里能解决就解决,不能解决就 ...

  7. 策略模式 Strategy 政策Policy 行为型 设计模式(二十五)

    策略模式 Strategy   与策略相关的常见词汇有:营销策略.折扣策略.教学策略.记忆策略.学习策略.... “策略”意味着分情况讨论,而不是一概而论 面对不同年龄段的人,面对不同的商品,必然将会 ...

  8. 中介者模式 调停者 Mediator 行为型 设计模式(二十一)

      中介者模式(Mediator)   调度.调停   意图 用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散 而且可以独立地改变它们之间的交互. ...

  9. 设计模式 ( 十二 ) 职责链模式(Chain of Responsibility)(对象行为)

     设计模式(十二)职责链模式(Chain of Responsibility)(对象行为型) 1.概述 你去政府部门求人办事过吗?有时候你会遇到过官员踢球推责,你的问题在我这里能解决就解决.不能解决就 ...

随机推荐

  1. 从壹开始微服务 [ DDD ] 之终篇 ║当事件溯源 遇上 粉丝活动

    回首 哈喽~大家好,时间过的真快,关于DDD领域驱动设计的讲解基本就差不多了,本来想着周四再开一篇,感觉没有太多的内容了,剩下的一个就是验证的问题,就和之前的JWT很类似,就不打开一个章节了,而且这个 ...

  2. 命令行中的 vi 模式

    命令行中修改已经输入的命令比较麻烦,如果你不知道一些快捷键的话,只能使用方向键一个一个字符地移动到目标位置进行修改,对于比较复杂且过长的命令来说,效率不高. 以下信息来自 bash 的 man 页面: ...

  3. React + TypeScript:元素引用的传递

    React 中需要操作元素时,可通过 findDOMNode() 或通过 createRef() 创建对元素的引用来实现.前者官方不推荐,所以这里讨论后者及其与 TypeScript 结合时如何工作. ...

  4. 简述Java变量和强制转换类型

    简述Java变量和强制转换类型 java变量 1. java变量 变量:顾名思义,就是在java执行程序过程中可以发生改变的量,就好比方程式中的未知数X一样. 变量的内存分配过程 int a ; // ...

  5. cesium 之地图贴地量算工具效果篇(附源码下载)

    前言 cesium 官网的api文档介绍地址cesium官网api,里面详细的介绍 cesium 各个类的介绍,还有就是在线例子:cesium 官网在线例子,这个也是学习 cesium 的好素材. 内 ...

  6. Android之日志管理(Log)

    ##文章大纲一.为什么要使用日志管理工具二.日志管理工具实战三.项目源码下载 ##一.为什么要使用日志管理工具###1. 对IT安全至关重要  当您使用强大的日志管理软件自动触发以保护您的系统时,您已 ...

  7. 4. [mmc subsystem] mmc core(第四章)——host模块说明

    零.说明 对应代码drivers/mmc/core/host.c,drivers/mmc/core/host.h. 为底层host controller driver实现mmc host的申请以及注册 ...

  8. Windows Server 2012 R2 安装密钥(只适用安装,不支持激活)

    标准版 = NB4WH-BBBYV-3MPPC-9RCMV-46XCB 数据中心版 = BH9T4-4N7CW-67J3M-64J36-WW98Y

  9. scrapy-redis 分布式爬虫

    为什么要学? Scrapy_redis在scrapy的基础上实现了更多,更强大的功能. 有哪些功能体现? request去重.爬虫持久化.实现分布式爬虫.断点续爬(带爬取的request存在redis ...

  10. MongoDB安装与使用体验

    1.获取并安装 具体的安装包可以到官方网站下载:http://www.mongodb.org/downloads 我看着教程就下载了linux版本吧,也不是很复杂.包的体积有点大. 安装过程比较简单, ...