音效素材网提供各类素材,打造精品素材网站!

站内导航 站长工具 投稿中心 手机访问

音效素材

深入解析iOS应用开发中对设计模式中的桥接模式的使用
日期:2016-03-20 21:30:29   来源:脚本之家

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


何为桥接模式?

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

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

桥接模式的实例应用

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

2016320212809483.jpg (680×502)

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

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

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

复制代码 代码如下:

#import <Foundation/Foundation.h>
#import "TVProtocol.h"
@interface AbstractRemoteControl : NSObject
 
@property (nonatomic, weak) id<TVProtocol> 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 <Foundation/Foundation.h>
 
@protocol TVProtocol <NSObject>
 
@required
 
- (void)switchChannel; // 切换频道
 
- (void)adjustVolume;  // 调节音量
 
- (void)powerSwitch;   // 电源开关
 
@end
    这就是一个协议,协议里面定义了三个方法,以后在创建电视机实例的时候,就必须遵守该协议,从而保证了电视机具有相同的功能。AbstractTV的代码如下:

#import <Foundation/Foundation.h>
#import "TVProtocol.h"
@interface AbstractTV : NSObject <TVProtocol>
 
@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; //在这里要进行一下转换声明,否则类中不能识别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

    日志输出如下:

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。这正是桥接模式带给我们的便利性。
   
小结

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

桥接模式的优点:

桥接模式使用聚合关系,解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。
提高了系统的可扩展性,可以独立地对抽象部分和实现部分进行扩展。
可减少子类的个数,这个在前面讲手机示例的时候进行分析了。

桥接模式的缺点:
桥接模式的引入会增加系统的理解与设计难度,由于聚合关系建立在抽象层,要求开发者针对抽象进行设计与编程。
桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

适用场景
通过优缺点的分析,我们可以在如下的情形下使用桥接模式:
不想在抽象与其实现之间形成固定的绑定关系;
抽象及其实现都应可以通过子类化独立进行扩展;
对抽象的实现进行修改不应影响客户端代码;
如果每个实现需要额外的子类以细化抽象,则说明有必要把它们分成两个部分;
想在带有不同抽象接口的多个对象之间共享一个实现。

    您感兴趣的教程

    在docker中安装mysql详解

    本篇文章主要介绍了在docker中安装mysql详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编...

    详解 安装 docker mysql

    win10中文输入法仅在桌面显示怎么办?

    win10中文输入法仅在桌面显示怎么办?

    win10系统使用搜狗,QQ输入法只有在显示桌面的时候才出来,在使用其他程序输入框里面却只能输入字母数字,win10中...

    win10 中文输入法

    一分钟掌握linux系统目录结构

    这篇文章主要介绍了linux系统目录结构,通过结构图和多张表格了解linux系统目录结构,感兴趣的小伙伴们可以参考一...

    结构 目录 系统 linux

    PHP程序员玩转Linux系列 Linux和Windows安装

    这篇文章主要为大家详细介绍了PHP程序员玩转Linux系列文章,Linux和Windows安装nginx教程,具有一定的参考价值,感兴趣...

    玩转 程序员 安装 系列 PHP

    win10怎么安装杜比音效Doby V4.1 win10安装杜

    第四代杜比®家庭影院®技术包含了一整套协同工作的技术,让PC 发出清晰的环绕声同时第四代杜比家庭影院技术...

    win10杜比音效

    纯CSS实现iOS风格打开关闭选择框功能

    这篇文章主要介绍了纯CSS实现iOS风格打开关闭选择框,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作...

    css ios c

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的办法

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的

    Win7给电脑C盘扩容的办法大家知道吗?当系统分区C盘空间不足时,就需要给它扩容了,如果不管,C盘没有足够的空间...

    Win7 C盘 扩容

    百度推广竞品词的投放策略

    SEM是基于关键词搜索的营销活动。作为推广人员,我们所做的工作,就是打理成千上万的关键词,关注它们的质量度...

    百度推广 竞品词

    Visual Studio Code(vscode) git的使用教程

    这篇文章主要介绍了详解Visual Studio Code(vscode) git的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...

    教程 Studio Visual Code git

    七牛云储存创始人分享七牛的创立故事与

    这篇文章主要介绍了七牛云储存创始人分享七牛的创立故事与对Go语言的应用,七牛选用Go语言这门新兴的编程语言进行...

    七牛 Go语言

    Win10预览版Mobile 10547即将发布 9月19日上午

    微软副总裁Gabriel Aul的Twitter透露了 Win10 Mobile预览版10536即将发布,他表示该版本已进入内部慢速版阶段,发布时间目...

    Win10 预览版

    HTML标签meta总结,HTML5 head meta 属性整理

    移动前端开发中添加一些webkit专属的HTML5头部标签,帮助浏览器更好解析HTML代码,更好地将移动web前端页面表现出来...

    移动端html5模拟长按事件的实现方法

    这篇文章主要介绍了移动端html5模拟长按事件的实现方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家...

    移动端 html5 长按

    HTML常用meta大全(推荐)

    这篇文章主要介绍了HTML常用meta大全(推荐),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参...

    cdr怎么把图片转换成位图? cdr图片转换为位图的教程

    cdr怎么把图片转换成位图? cdr图片转换为

    cdr怎么把图片转换成位图?cdr中插入的图片想要转换成位图,该怎么转换呢?下面我们就来看看cdr图片转换为位图的...

    cdr 图片 位图

    win10系统怎么录屏?win10系统自带录屏详细教程

    win10系统怎么录屏?win10系统自带录屏详细

    当我们是使用win10系统的时候,想要录制电脑上的画面,这时候有人会想到下个第三方软件,其实可以用电脑上的自带...

    win10 系统自带录屏 详细教程

    + 更多教程 +
    Windows系统Linux系统苹果MACAndroidiOS系统鸿蒙系统