iOS设计模式-适配器模式

1. 什么是适配器

适配器模式(Adapter Pattern) 定义

Convert the interface of a class into another interface clients expect.Adapter lets classeswork together that couldn't otherwise because of incompatible interfaces.(将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。)

有时候也称包装样式或者包装(wrapper)。将一个类的接口转接成用户所期待的。一个适配使得因接口不兼容而不能在一起工作的类能在一起工作,做法是将类自己的接口包裹在一个已存在的类中。

2. 角色组成

适配器设计模型主要由三个角色组成,分别是:

  • 适配器角色(adapter)-- 将已有接口转换成目标接口
  • 目标角色(target) -- 目标可以是具体的或抽象的类,也可以是接口
  • 源角色或被适配者(adaptee) -- 已有接口

3. 适配器类型与实例

适配器类型可以分为三种:

  • 类适配器模式
  • 对象适配器模式
  • 接口适配器模式或缺省适配器模式

接下来我们以手机充电适配器为例子,手机充电需要的电压一般为5V。国内家用供电为220V,而日本则是110V,针对不同地区的电压,我们需要一个适配器,这样才能对手机进行充电。

我们先定义电源基础类

//  Power.h
#import <Foundation/Foundation.h>
@interface Power : NSObject
//输出电压
- (NSInteger)outputValue;
@end


// Power.m
#import "Power.h"
@implementation Power
- (NSInteger)outputValue {
    return 0;
}
@end

定义好适配手机5V电压的协议

// PowerPhoneNeedInterface.h

#import <Foundation/Foundation.h>
@protocol PowerPhoneNeedInterface <NSObject>
@required
- (NSInteger)outputPowerPhone;
@end
3.1 类适配器模式

类适配器模式是通过继承被适配者来实现适配功能,UML关系图如下:


类适配器模式

代码如下:
我们先定义一个被适配者角色(中国家用供电):

// PowerACChina.h

#import "Power.h"
@interface PowerACChina : Power
@end


// PowerACChina.m

#import "PowerACChina.h"
@implementation PowerACChina
- (NSInteger)outputValue {
    return 220;
}
@end

接下来可以定义一个适配器(中国家用供电),来满足手机充电输出5V的要求

//PowerAdapterChina.h

#import "PowerACChina.h"
#import "PowerPhoneNeedInterface.h"
@interface PowerAdapterChina : PowerACChina<PowerPhoneNeedInterface>
@end


//PowerAdapterChina.m

#import "PowerAdapterChina.h"
@implementation PowerAdapterChina
- (NSInteger)outputPowerPhone {
    return [self outputValue] / 44;
}
@end

这里,我们的适配器,继承于被适配角色并且实现目标角色,这样通过实现目标角色中的方法调用被适配角色中的方法进行运算,从而达到适配的效果。接下来我们进行调用测试

+ (void)test1 {
    PowerAdapterChina *adapter = [[PowerAdapterChina alloc] init];
    NSInteger inputPower = [adapter outputPowerPhone];
    NSLog(@"source output power = %ld\n adpater output power = %ld", [adapter outputValue], inputPower);
}

运行结果:

source output power = 220 
adpater output power = 5

从代码中我们可以看到,其实适配器做的主要工作就是为了让被适配角色的API能够满足目标角色的要求进行调用,适配器在中间做的是一个类似的中转作用,并且不影响源角色和目标角色原有的功能和逻辑。

3.2 对象适配器模式

对象适配器模式适配器通过持有被适配角色实例,对接口进行适配,UML图如下:


对象适配器

接下来我们通过代码实现下。

被适配者角色,依然是 PowerACChina, 这里不再进行重复阐述。

接下来我们来创建一个新的适配器 PowerAdapterChina2


// PowerAdapterChina2.h

#import <Foundation/Foundation.h>
#import "PowerPhoneNeedInterface.h"
#import "PowerACChina.h"

@interface PowerAdapterChina2 : NSObject <PowerPhoneNeedInterface>
@property (nonatomic, strong) PowerACChina *power;
- (instancetype)initWithChinaPower:(PowerACChina *)power;
@end



// PowerAdapterChina2.m

#import "PowerAdapterChina2.h"
#import "PowerACChina.h"
@implementation PowerAdapterChina2
- (instancetype)initWithChinaPower:(PowerACChina *)power {
    self = [super init];
    if (self) {
        _power = power;
    }
    return self;
}
- (NSInteger)outputPowerPhone {
    return [self.power outputValue] / 44;
}
@end

有代码可以看到,我们需要在适配器PowerAdapterChina2通过持有被适配者调用源API达到转换成满足目标API的效果, 我们写个测试方法进行验证

//对象适配
+ (void)test2 {
    PowerACChina *power = [[PowerACChina alloc] init];
    PowerAdapterChina2 *adapter = [[PowerAdapterChina2 alloc] initWithChinaPower:power];
    NSInteger inputPower = [adapter outputPowerPhone];
    NSLog(@"source output power = %ld\n adpater output power = %ld", [power outputValue], inputPower);
}
source output power = 220 
adpater output power = 5

我们的测试时在创建适配器时,传入参数为被适配角色实例,通过适配器,输出目标

3.3 接口适配器模式

接口适配器模式又成缺省适配器模式,适配器默认实现了所有目标角色接口。并且适配器可以通过持有不同的被适配者实例,在内部进行转化为目标角色API,相对应前两种适配模式更加灵活且易于拓展。
UML关系图如下:


接口适配器

代码如下:
我们再建一个被适配者角色(日本家用供电) PowerACJapan

// PowerACJapan.h

#import "Power.h"
@interface PowerACJapan : Power
@end



//PowerACJapan.h

#import "PowerACJapan.h"
@implementation PowerACJapan
- (NSInteger)outputValue {
    return 110;
}
@end

定义接口适配器


// PowerAdapterAll.h
#import <Foundation/Foundation.h>
#import "Power.h"
#import "PowerPhoneNeedInterface.h"
@interface PowerAdapterAll : NSObject<PowerPhoneNeedInterface>
@property (nonatomic, strong) Power *power;
- (instancetype)initWithPower:(Power *)power;
@end


// PowerAdapterAll.m
#import "PowerAdapterAll.h"
#import "Power.h"
@implementation PowerAdapterAll
- (instancetype)initWithPower:(Power *)power {
    self = [super init];
    if (self) {
        _power = power;
    }
    return self;
}
- (NSInteger)outputPowerPhone {
    NSInteger outputPower = [self.power outputValue];
    NSInteger sta = outputPower / 5;
    if ([self.power isKindOfClass:PowerACJapan.class]) {
        sta = 22;
    } else if ([self.power isKindOfClass:PowerACChina.class]) {
        sta = 44;
    }
    return outputPower / sta;
}
@end

调用测试:

//接口适配
+ (void)test3 {
    PowerACChina *power = [[PowerACChina alloc] init];
    PowerAdapterAll *adapter = [[PowerAdapterAll alloc] initWithPower:power];
    NSInteger inputPower = [adapter outputPowerPhone];
    NSLog(@"source output power = %ld\n adpater output power = %ld", [adapter.power outputValue], inputPower);
    
    PowerACJapan *power2 = [[PowerACJapan alloc] init];
    PowerAdapterAll *adapter2 = [[PowerAdapterAll alloc] initWithPower:power2];
    NSInteger inputPower2 = [adapter2 outputPowerPhone];
    NSLog(@"source output power = %ld\n adpater output power = %ld", [adapter2.power outputValue], inputPower2);
}

运行结果

source output power = 220
 adpater output power = 5
source output power = 110
 adpater output power = 5

如上代码,在适配器角色中,我们提供多个适配器角色能够传入不同被适配者角色能力(这里传入的为Power子类),通过具体被适配者角色的实例使用抽象的电源引用,适配器类实现于目标角色并实现目标角色的方法,在方法体中,我们进行逻辑处理,将输入的电压进行适配为5V电压,从而达到万能适配的效果。

4. 分析

  • 复用性:系统需要使用已经存在的类,功能符合系统要求,但这个类的接口不符合系统的需求,通过适配器模式解决不兼容的问题,使这些功能类得到复用。
  • 耦合性:一定程度上的解耦
  • 过多地使用适配器,增加系统理解难度。

适用场景

  • 我们在使用第三方的类库,或者说第三方的API的时候,我们通过适配器转换来满足现有系统的使用需求。

  • 你想使用现有的一些类的功能,但其接口不匹配你的要求,你又不想修改原始类的情况

  • 需要建立一个可以重复使用的类,用于一些彼此关系不大的类,并易于扩展,以便于面对将来会出现的类。

  • 需要一个统一的输出接口,但是输入类型却不可预知。

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