桥接模式

引言

在项目开发中,我们会遇到这样的一种场景:某些类型由于自身的逻辑,往往具有两个或多个维度的变化,比如说大话设计模式书中所说的手机,它有两个变化的维度:一是手机的品牌,可能有三星、苹果等;二是手机上的软件,可能有QQ、微信等。如何应对这种“多维度的变化”?怎样利用面向对象的技术来使得该类型能够轻松的沿着多个方向进行变化,而又不引入额外的复杂度?这就是本章桥接模式所要解决的问题。

何为桥接模式?

桥接模式的目的是把抽象层次结构从其实现中分离出来,使其能够独立变更。抽象层定义了供客户端使用的上层的抽象接口。实现层定义了供抽象层使用的底层接口。实现类的引用被封装于抽象层的实例中,桥接就形成。(与外观模式有一定的相似之处)。

桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。

桥接模式的实例应用

比如有一家电视机制造商,他们生产的每台电视都带一个遥控器,用户可以用遥控器进行频道切换之类的操作。在这里遥控器是控制电视机的接口,如果每个电视机型号需要一个专用的遥控器,那么单是遥控器就会导致设计激增。不过,每个遥控器都有些功能是各种型号电视机共有的,比如切换频道、调节音量和电源开关。而且每台电视机都应该能够通过基本命令接口,响应遥控器发来的这些命令。我们可以把遥控器逻辑同实际的电视机型号分离开来。这样电视机型号的改变就不会对遥控器的设计有任何的影响。遥控器的同一个设计可以被复用和扩展,而不会影响其他电视机型号。如下图所示:

AbstractRemoteControl是定义了供客户端使用的上层接口的父接口。它有一个对TVProtocol视力的引用,TVProtocol定义了实现类的接口。这个接口不必跟AbstractRemoteControl的接口一致,其实两个接口可以完全不同。TVProtocol的接口提供基本的操作,而AbstractRemoteControl的上层操作基于这些基本操作。当客户端向AbstractRemoteControl的实例发送operation消息时,这个方法向imp发送operationImp消息,底下的实际由TVA或TVB将作出响应并接受任务。

因此想要往系统中添加新的TVProtocol时,所要做的只是为TVProtocol创建一个新的实现类,响应operationImp消息并在其中执行任何具体的操作。不过,这对AbstractRemoteControl方面不会有任何影响。同样,如果想修改AbstractRemoteControl的接口或者创建更细化的AbstractRemoteControl类,也不会影响桥接的另一头。

来看下具体的代码实现,先看下抽象部分的代码实现,AbstractRemoteControl代码如下:

复制代码代码如下:

#import

#import "TVProtocol.h"

@interface AbstractRemoteControl : NSObject

@property (nonatomic, weak) id tvProtocol;

- (void)detectTVFunction;

@end

复制代码代码如下:

#import "AbstractRemoteControl.h"

@implementation AbstractRemoteControl

- (void)detectTVFunction {

NSLog(@"检测电视机具备的功能,由子类来进行实现");

}

@end

在AbstractRemoteControl类中保持了对TVProtocol实例对象的引用,定义了供客户端使用的上层抽象接口detectTVFunction,而这个方法的具体实现则由其子类去实现,ConcreteRemoteControl代码如下:

复制代码代码如下:

#import "AbstractRemoteControl.h"

@interface ConcreteRemoteControl : AbstractRemoteControl

// 重写该方法

- (void)detectTVFunction;

@end

复制代码代码如下:

#import "ConcreteRemoteControl.h"

@implementation ConcreteRemoteControl

- (void)detectTVFunction {

[self.tvProtocol switchChannel];

[self.tvProtocol adjustVolume];

[self.tvProtocol powerSwitch];

}

@end

从这里我们可以看出,当客户端向ConcreteRemoteControl的实例发送detectTVFunction消息时,这个方法向TVProtocol发送switchChannel、adjustVolume、powerSwitch三个消息,TVA或TVB将作出响应并接受任务。至此,抽象部分代码已经完成了,接着看下实现部分的代码,TVProtocol代码如下:

复制代码代码如下:

#import

@protocol TVProtocol

@required

- (void)switchChannel; // 切换频道

- (void)adjustVolume;  // 调节音量

- (void)powerSwitch;   // 电源开关

@end

这就是一个协议,协议里面定义了三个方法,以后在创建电视机实例的时候,就必须遵守该协议,从而保证了电视机具有相同的功能。AbstractTV的代码如下:

#import

#import "TVProtocol.h"

@interface AbstractTV : NSObject

@end

复制代码代码如下:

#import "AbstractTV.h"

@implementation AbstractTV

- (void)switchChannel {

NSLog(@"切换频道,由具体的子类来实现");

}

- (void)adjustVolume {

NSLog(@"调节音量,由具体的子类来实现");

}

- (void)powerSwitch {

NSLog(@"电源开关,由具体的子类来实现");

}

@end

TVA的代码如下:

复制代码代码如下:

#import "AbstractTV.h"

@interface TVA : AbstractTV

// 重写这三个方法

- (void)switchChannel;

- (void)adjustVolume;

- (void)powerSwitch;

@end

复制代码代码如下:

#import "TVA.h"

@implementation TVA

- (void)switchChannel {

NSLog(@"电视机A 具备了切换频道的功能");

}

- (void)adjustVolume {

NSLog(@"电视机A 具备了调节音量的功能");

}

- (void)powerSwitch {

NSLog(@"电视机A 具备了电源开关的功能");

}

@end

TVB的代码如下:

复制代码代码如下:

#import "AbstractTV.h"

@interface TVB : AbstractTV

// 重写这三个方法

- (void)switchChannel;

- (void)adjustVolume;

- (void)powerSwitch;

@end

复制代码代码如下:

#import "TVB.h"

@implementation TVB

- (void)switchChannel {

NSLog(@"电视机B 具备了切换频道的功能");

}

- (void)adjustVolume {

NSLog(@"电视机B 具备了调节音量的功能");

}

- (void)powerSwitch {

NSLog(@"电视机B 具备了电源开关的功能");

}

@end

到这里,桥接模式代码已经完成了,在客户端该怎么去应用呢?我们通过下面的客户端代码来说明,如下:

复制代码代码如下:

#import "ViewController.h"

#import "AbstractRemoteControl.h"

#import "ConcreteRemoteControl.h"

#import "TVProtocol.h"

#import "AbstractTV.h"

#import "TVA.h"

#import "TVB.h"

typedef id TVProtocol; //在这里要进行一下转换声明,否则类中不能识别TVProtocol.

@interface ViewController ()

@end

复制代码代码如下:

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

AbstractRemoteControl *remoteControl = [[ConcreteRemoteControl alloc] init];

TVProtocol tvProtocol = [[TVA alloc] init];

remoteControl.tvProtocol = tvProtocol;

[remoteControl detectTVFunction];

NSLog(@"///////////////////////////////");

tvProtocol = [[TVB alloc] init];

remoteControl.tvProtocol = tvProtocol;

[remoteControl detectTVFunction];

/**

*  桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。

*  在本例中,AbstractRemoteControl是抽象部分,TVProtocol是其实现部分。

*/

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

@end

日志输出如下:

?

1

2

3

4

5

6

7

2015-09-01 22:59:06.295 Bridge[16464:703747] 电视机A 具备了切换频道的功能

2015-09-01 22:59:06.295 Bridge[16464:703747] 电视机A 具备了调节音量的功能

2015-09-01 22:59:06.296 Bridge[16464:703747] 电视机A 具备了电源开关的功能

2015-09-01 22:59:06.296 Bridge[16464:703747] ///////////////////////////////

2015-09-01 22:59:06.296 Bridge[16464:703747] 电视机B 具备了切换频道的功能

2015-09-01 22:59:06.296 Bridge[16464:703747] 电视机B 具备了调节音量的功能

2015-09-01 22:59:06.296 Bridge[16464:703747] 电视机B 具备了电源开关的功能

通过桥接模式的应用,我们可以把抽象部分与实现部分分离,使它们都可以独立的变化。比如在本例中,对AbstractRemoteControl的修改,不会影响到TVProtocol。同样对TVProtocol的修改,也不会影响AbstractRemoteControl。这正是桥接模式带给我们的便利性。

小结

总的来说,桥接模式的本质在于“分离抽象和实现”。

桥接模式的优点:

桥接模式使用聚合关系,解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。

提高了系统的可扩展性,可以独立地对抽象部分和实现部分进行扩展。

可减少子类的个数,这个在前面讲手机示例的时候进行分析了。

桥接模式的缺点:

桥接模式的引入会增加系统的理解与设计难度,由于聚合关系建立在抽象层,要求开发者针对抽象进行设计与编程。

桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

适用场景

通过优缺点的分析,我们可以在如下的情形下使用桥接模式:

不想在抽象与其实现之间形成固定的绑定关系;

抽象及其实现都应可以通过子类化独立进行扩展;

对抽象的实现进行修改不应影响客户端代码;

如果每个实现需要额外的子类以细化抽象,则说明有必要把它们分成两个部分;

想在带有不同抽象接口的多个对象之间共享一个实现。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 发送提示消息## 考虑这样一个实际的业务功能:发送提示消息。基本上所有带业务流程处理的系统...
    七寸知架构阅读 4,802评论 5 63
  • 在正式介绍桥接模式之前,我先跟大家谈谈两种常见文具的区别,它们是毛笔和蜡笔。假如我们需要大中小3种型号的画笔,能够...
    justCode_阅读 1,714评论 0 7
  • 目录 本文的结构如下: 引言 什么是桥接模式 模式的结构 典型代码 代码示例 优点和缺点 适用环境 模式应用 一、...
    w1992wishes阅读 1,742评论 0 6
  • 本文参考:http://www.oschina.net/question/1436074_140456 http:...
    端木轩阅读 3,550评论 0 5
  • 一些没有错误的原因 之前写了一次《我所熟知的两种愚昧》,这大概可以被认为是这篇文章的精神续篇。 今天早上我说 感觉...
    程序员Delton阅读 757评论 0 1