Objective-C设计模式解析-桥接

看图识模式

图1
clipboard.png

或者这样

图2
clipboard.png

提出问题

问题假设: 这里的遥控器和电视剧都是绑定的,一个遥控器对应一个电视机。

有以下问题:

  • 有2条生产线,一条产线生产遥控器,一条产线生成电视机

  • 不同电视机的结构是不一样的,遥控器也要随市场需求去变化

  • 问题来了,我要生产一台新电视,怎么办

  • 问题来了,我要定制一个新的遥控器,所有的电视都要适配

按照上面的结构图,哪个一更好呢(图1?图2?)

设计原则

在面向对象设计中,我们还有一个很重要的设计原则,那就是合成/聚合复用原则。即有限使用对象合成/聚合,而不是类继承。

合成/聚合复用的好处是,优先使用对象的合成/聚合将有助于你保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。

因为子类的实现与它的父类有非常紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类是,如果继承下来的实现不适合解决新问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。

怎样上面的改进

根据实际需要对遥控器和电视进行组合:
我们抽象出一个“遥控器”类和“电视”类,让不同的遥控器和电视机分别继承它们,这样要增加新的遥控器或则新的电视品牌就不会影响其他类。遥控器应该包含电视剧,但是电视机不是遥控器的一部分,所以它们之间是聚合关系。

clipboard.png

模式动机

  1. 对于有两个变化维度(或则多个维度)的系统,采用组合方式系统中类的个数更少,且系统扩展更为方便。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。

  2. 把这种多角度分类分离出来,让它们独立变化,减少它们之间耦合。

模式定义

桥接模式(Bridge Pattern):

将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

把抽象层次结构从实现中分离出来,使其可以独立变更,抽象层定义了供客户端使用的上层抽象接口,实现层次结构定义了供抽象层次使用的底层接口,实现类的引用被封装于抽象层的实例中,桥接就形成了。

模式结构

clipboard.png

桥接模式包含如下角色:

  • Abstraction 抽象的接口(类),该接口包含实现具体行为、具体特征的Implementor接口(类)

  • Refined Abstraction 抽象接口Abstraction的子类

  • Implementor 定义具体行为、具体特征的应用接口(实现类接口)

  • ConcreteImplementor 实现Implementor接口

代码分析

第一部分: 抽象的系统接口
@protocol System <NSObject>

- (void)on;
- (void)off;
- (void)setChannel:(NSInteger)ch;
- (void)setVolume:(NSInteger)vol;

@end

有2个实现系统接口的电视:

LG电视

@interface LGSystem : NSObject <System>

@end

@implementation LGSystem

- (void)on
{
    NSLog(@"~~~~~LGTV: 打开");
}

- (void)off
{
    NSLog(@"~~~~~LGTV: 关闭");
}

- (void)setChannel:(NSInteger)ch
{
    NSLog(@"~~~~~LGTV: 频道:%@", @(ch));
}

- (void)setVolume:(NSInteger)vol
{
    NSLog(@"~~~~~LGTV: 音量%@", @(vol));
}

@end

三星电视

@interface SamsungSystem : NSObject <System>

@end 

@implementation SamsungSystem

- (void)on
{
    NSLog(@"~~~~~SamsungTV: 打开");
}

- (void)off
{
    NSLog(@"~~~~~SamsungTV: 关闭");
}

- (void)setChannel:(NSInteger)ch
{
    NSLog(@"~~~~~SamsungTV: 频道:%@", @(ch));
}

- (void)setVolume:(NSInteger)vol
{
    NSLog(@"~~~~~SamsungTV: 音量:%@", @(vol));
}

@end
第二部分: 抽象的控制类

抽象的控制类

@interface TVControl : NSObject

@property (nonatomic, strong) id <System>systemImp;

- (instancetype)initWithSystem:(id <System>)system;

- (void)Onoff;
- (void)nextChannel;
- (void)preChannel;

@end

@implementation TVControl

- (instancetype)initWithSystem:(id <System>)system
{
    self = [super init];
    if (self) {
        _systemImp = system;
    }
    return self;
}

- (void)Onoff
{
    // 抽象方法
}

- (void)nextChannel
{
    // 抽象方法
}

- (void)preChannel
{
    // 抽象方法
}

@end

下面有一个简单控制器类:

.h
@interface SimpleTVControl : TVControl
@end

.m
@interface SimpleTVControl ()

@property (nonatomic, assign) BOOL isOn;
@property (nonatomic, assign) NSInteger channel;

@end

@implementation SimpleTVControl

- (void)Onoff
{
    if (self.isOn) {
        [self.systemImp off];
    } else {
        [self.systemImp on];
    }
    self.isOn = !self.isOn;
}

- (void)nextChannel
{
    self.channel++;
    [self.systemImp setChannel:self.channel];
}

- (void)preChannel
{
    self.channel--;
    [self.systemImp setChannel:self.channel];
}
    
@end

现在添加一个多功能的控制器类:
他多了2个新功能,一、回看功能。二、设置频道。

@interface MultifunctionTVControl : TVControl

- (void)back;
- (void)channel:(NSInteger)ch;

@end

@interface MultifunctionTVControl ()

@property (nonatomic, assign) BOOL isOn;
@property (nonatomic, assign) NSInteger channel;
@property (nonatomic, assign) NSInteger preCh;

@end

@implementation MultifunctionTVControl

- (void)Onoff
{
    if (self.isOn) {
        [self.systemImp off];
    } else {
        [self.systemImp on];
    }
    self.isOn = !self.isOn;
}

- (void)nextChannel
{
    self.channel++;
    [self.systemImp setChannel:self.channel];
    self.preCh = self.channel;
}

- (void)preChannel
{
    self.channel--;
    [self.systemImp setChannel:self.channel];
    self.preCh = self.channel;
}

- (void)back
{
    [self.systemImp setChannel:self.preCh];
}

- (void)channel:(NSInteger)ch
{
    self.preCh = ch;
    self.channel = ch;
    [self.systemImp setChannel:self.channel];
}

@end
client调用
    // 简单遥控器
    LGSystem *lgSys = [LGSystem new];
    SimpleTVControl *lgControl = [[SimpleTVControl alloc] initWithSystem:lgSys];
    [lgControl Onoff];
    [lgControl nextChannel];
    [lgControl nextChannel];
    [lgControl preChannel];
    [lgControl Onoff];
    
    SamsungSystem *samSys = [SamsungSystem new];
    SimpleTVControl *samControl = [[SimpleTVControl alloc] initWithSystem:samSys];
    [samControl Onoff];
    [samControl nextChannel];
    [samControl nextChannel];
    [samControl preChannel];
    [samControl Onoff];
    
    // 多功能遥控器
    MultifunctionTVControl *lgMultifunControl = [[MultifunctionTVControl alloc] initWithSystem:lgSys];
    [lgMultifunControl Onoff];
    [lgMultifunControl channel:18];
    [lgMultifunControl preChannel];
    [lgMultifunControl back];
    [lgMultifunControl Onoff];
    
    
    // 多功能遥控器
    MultifunctionTVControl *samMultifunControl = [[MultifunctionTVControl alloc] initWithSystem:samSys];
    [samMultifunControl Onoff];
    [samMultifunControl channel:18];
    [samMultifunControl preChannel];
    [samMultifunControl back];
    [samMultifunControl Onoff];

使用场景

  • 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。

  • 抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。

  • 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。

  • 虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。

  • 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

优点

桥接模式的优点:

  • 分离抽象接口及其实现部分。

  • 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。

  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。

  • 实现细节对客户透明,可以对用户隐藏实现细节。

缺点

桥接模式的缺点:

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

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

总结

  • 桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。

  • 桥接模式包含如下四个角色:抽象类中定义了一个实现类接口类型的对象并可以维护该对象;扩充抽象类扩充由抽象类定义的接口,它实现了在抽象类中定义的抽象业务方法,在扩充抽象类中可以调用在实现类接口中定义的业务方法;实现类接口定义了实现类的接口,实现类接口仅提供基本操作,而抽象类定义的接口可能会做更多更复杂的操作;具体实现类实现了实现类接口并且具体实现它,在不同的具体实现类中提供基本操作的不同实现,在程序运行时,具体实现类对象将替换其父类对象,提供给客户端具体的业务操作方法。

  • 在桥接模式中,抽象化(Abstraction)与实现化(Implementation)脱耦,它们可以沿着各自的维度独立变化。

  • 桥接模式的主要优点是分离抽象接口及其实现部分,是比多继承方案更好的解决方法,桥接模式还提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,实现细节对客户透明,可以对用户隐藏实现细节;其主要缺点是增加系统的理解与设计难度,且识别出系统中两个独立变化的维度并不是一件容易的事情。

  • 桥接模式适用情况包括:需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系;抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响;一个类存在两个独立变化的维度,且这两个维度都需要进行扩展;设计要求需要独立管理抽象化角色和具体化角色;不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统。


小daniel的笔记本
iOS开发者,独立撰稿人。从票务电商行业开始,沉浸移动端开发。

程序猿的逗比日常-------

28 声望
8 粉丝
0 条评论
推荐阅读
Objective-C设计模式解析-享元
如果要去100个目的地,每个目的地派遣1辆车,那么总计需要100辆车。比如北上广深等一线城市每天几千万次出行,没人一辆车城市不就瘫痪了嘛。。。。

danielmea3阅读 2.1k

工作中常用的设计模式--享元模式
一般做业务开发,不太容易有大量使用设计模式的场景。这里总结一下在业务开发中使用较为频繁的设计模式。当然语言为Java,基于Spring框架。

lpe2344阅读 962

封面图
iOS 健康共享失败如何解决
您要开始与之共享的对象必须已经连同他们的 iCloud 账户邮箱一起保存在您的“通讯录”中(iCloud 账户邮箱即 iCloud 账户绑定的邮箱信息,不是强制要求 @iCloud.com 邮箱)。

岚哲阅读 4.8k

探究 iOS 内存问题
本文从 Tagged Pointer、objc 源码、dealloc 原理、AutoreleasePool 原理、野指针探究等技术点展开聊了聊 iOS 内存相关问题。

杭城小刘1阅读 1.3k

封面图
面试的时候别再说你不会设计模式了
最近在设计一个对某个中间件的测试方案,这个测试方案需要包含不同的测试逻辑,但相同的是需要对各个环节进行记录;比如统计耗时、调用通知 API 等相同的逻辑。

crossoverJie阅读 1k

设计模式那些事(4)——常见结构型模式在Go中的应用
上一篇创建型(单例/工厂/建造者)设计模式在Go中的应用介绍了一些常见的创建型设计模式,创建型主要解决了类的创建问题,使代码更易用,而我们经常遇到另外一种问题:类或对象如何组合在一起效率更高,结构型模...

爆裂Gopher1阅读 601评论 1

封面图
iOS IDA逆向之patch
这里介绍的是ida的patch.1.搜索svc #0x80,回到IDA View-A界面,才能正确搜索点击图中T字按钮,弹出搜索框点击列表中进入2.修改svc #0x80,在IDA View-A界面中选中svc那一行,点击工具栏Edit--&gt;Patch program--&gt...

宋冬野阅读 2k

程序猿的逗比日常-------

28 声望
8 粉丝
宣传栏