Notes and thoughts from Tony

iOS中的设计模式——适配器(Adapter)

Comments

适配器模式

适配器模式(Adapter:将一个类的接口转换成客户端希望的另外一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适配器用于连接两种不同种类的对象,使其毫无问题的协同工作。有时也称作为包装器Wapper。思想很简单,适配器实现客户端所要的某种接口的行为。同时,它又连接到另一个具有(完全)不同接口与行为的对象。一边是客户端懂得如何使用的目标接口,另一边是客户端一无所知的被适配者,适配器处于两者之间。适配器的主要作用是把被适配者的行为传递给管道另一端的客户端。

类图

基本上有两种实现适配器的方式。

第一种是通过继承来适配两个接口,这称为类适配器。C++中类适配器是通过多重继承实现的。在JavaOC这类没有多继承的语言中,可以通过实现接口或协议,同时继承父类来实现类适配。具体在OC中,首先需要有定义了客户端要是用的一套行为的协议,然后要用具体的适配器来实现这个协议。适配器类同时也要继承被适配者。

Adapter

Target指目标接口。 Adaptee指被适配者。 request意为请求。

Adapter是一个Target类型,同时也是一个Adaptee类型。Adapter重载Targetrequest方法。但是Adapter没有重载AdapteespecficRequest方法,而是在Adapterrequest方法中的实现中,调用父类specficRequest方法。request方法在运行时向父类发送[super specficRequest]消息。super就是Adaptee,它的Adapterrequest方法的作用域内,按自己的方式执行specficRequest方法。只有当Target是协议而不是类时,类适配器才能够用OC来实现。

第二种称为对象适配器。与类适配器不同,对象适配器不继承被适配者,而是组合了一个对它的引用。实现为对象适配器时,它们之间的关系为:

Adapter1

TargetAdapter之间的关系与类适配器相同,而AdapterAdaptee之间的关系从“属于”变成了“包含”。这种关系下,Adapter需要保持一个对Adaptee的引用。在request方法中,Adapter发送[adaptee specficRequest]消息给引用adaptee,以间接访问它的行为,然后实现客户端请求的其余部分。由于AdapterAdaptee之间是一种“包含”关系,用Adapter去适配Adaptee的子类也没什么问题。

类适配器与对象适配器的对比

类适配器与对象适配器是实现适配器模式的不同方式,但是达成了同样的目的。

类适配器 对象适配器
只针对单一的具体Adaptee类,
Adaptee适配到Target
可以适配多个Adaptee及其子类
易于重载Adaptee的行为,因为
是通过直接的子类化进行的适配
难以重载Adaptee的行为,需要
借助于子类的对象而不是Adaptee本身
只有一个Adapter对象,无需额
外的指针间接访问Adaptee
需要额外的指针以间接访问
Adaptee并适配其行为

显然,代理(Delegate)模式属于对象适配器。

使用场景

  • 已有类的接口与需求不匹配。

  • 想要一个可复用的类,该类能够同可能带有不兼容接口的其他类协作。

  • 需要适配一个类的几个不同子类,可是每一个子类去子类化一个类适配器又不现实。那么可以使用对象适配器(也就是代理)来适配其父类的接口。

使用OC协议实现适配器模式

类适配器

Adapter2

//Charger.h 普通充电器
@interface Charger : NSObject
- (void)charge;//普通充电
@end

//Charger.m
@implementation Charger
- (void)charge {
    NSLog(@"充电中");
}
@end

//LightningChargerAdapterProtocol.h 适配接口
@protocol LightningChargerAdapterProtocol <NSObject>
@required
- (void)chargeWithLightning;//声明适配方法
@end

//LightningChargerAdapter.h  适配器,继承Charger,实现LightningChargerAdapterProtocol
@interface LightningChargerAdapter : Charger <LightningChargerAdapterProtocol>
@end

//LightningChargerAdapter.m
@implementation LightningChargerAdapter
- (void)chargeWithLightning {
    NSLog(@"使用Lightning");
    [super charge];//调用父类的充电方法
}
@end

//客户端调用
LightningChargerAdapter *charger = [[LightningChargerAdapter alloc] init];
[charger chargeWithLightning];

对象适配器

如果我们将MicroUSB接口的适配器只是作为充电器的一个组成部分,这样就实现了对象适配器。

Adapter3

//MicroUSBAdapterProtocol
@protocol MicroUSBAdapterProtocol <NSObject>
@required
- (void)chargeWithMicroUSB;
@end

//MicroUSBAdapter.h
@interface MicroUSBAdapter : NSObject
- (void)chargeWithMicroUSB;
@end

//MicroUSBAdapter.m
@interface MicroUSBAdapter() <MicroUSBAdapterProtocol>
@property (nonatomic, strong) Charger *charger;//持有Charger属性
@end

@implementation MicroUSBAdapter
- (void)chargeWithMicroUSB {
    NSLog(@"使用MicroUSB");
    self.charger = [[Charger alloc] init];
    [self.charger charge];
}
@end

Cocoa中更复杂的应用

之前提到了委托实际是一种适配器关系,确切来说是对象适配器。

Adapter4

UITableView的使用为例:想要把UITableView的接口变换成客户端需要的接口。这里的客户端是什么呢?其实就是UITableView。那么此处什么是Target(目标接口)呢?是一个UITableViewDelegateUITableViewDataSource。实现协议的具体类(UIViewController)会是个适配器。那什么是与框架不匹配而需要适配的类呢——应用程序(UIViewController)中的其他类。

我们之所以说委托机制主要是适配器模式,是因为委托机制可以实现某些其他设计模式的意图,比如装饰模式。委托模式的实现有时会跟其他设计模式混在一起。

总结

适配器模式的有点很多,如:将客户端与适配者解耦、使客户端调用更加简明(只需要调用定义的接口方法)。但同样也有缺点,在写适配器的时候为适应目标接口,可能会有比较复杂实现过程;无端端多出许多的类,使结构又复杂了一些。所以在使用的时候需要因情况而定,不要生搬硬套盲目的使用设计模式。

参考:

iOS Design Patterns

代码

文章中的代码都可以从我的GitHub DesignPatterns找到。