适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能.举个例子,读卡器是作为内存卡和笔记本之间的适配器,您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡
介绍
意图: 将一个类的接口转换成客户希望的另外一个接口.适配器模式使得原来由于接口不兼容而不能一起工作的那些类可以一起工作
主要解决:在系统软件中,常常将一些"现存的现象"放到新的环境中,而新环境要求的接口是现对象不能满足的
何时使用:
1. 系统需要使用现有的类,而此类的接口不符合系统的需要
2. 想要简历一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口
3. 通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
如何解决:继承或依赖(推荐)
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类
类图
基本上有两种实现适配器的方式。
- 通过继承来适配两个接口,这称为类适配器,C++中类适配器是通过多重继承实现的。在Java和OC这类没有多继承的语言中,可以通过实现接口或协议,同时继承父类来实现类适配。具体在OC中,首先需要有定义了客户端要是用的一套行为的协议,然后要用具体的适配器来实现这个协议。适配器类同时也要继承被适配者。
Target指目标接口。 Adaptee指被适配者。 request意为请求。
Adapter是一个Target类型,同时也是一个Adaptee类型。Adapter重载Target的request方法。但是Adapter没有重载Adaptee的specficRequest方法,而是在Adapter的request方法中的实现中,调用父类specficRequest方法。request方法在运行时向父类发送[super specficRequest]消息。super就是Adaptee,它的Adapter在request方法的作用域内,按自己的方式执行specficRequest方法。只有当Target是协议而不是类时,类适配器才能够用OC来实现
- 对象适配器。与类适配器不同,对象适配器不继承被适配者,而是组合了一个对它的引用。实现为对象适配器时,它们之间的关系为:
Target和Adapter之间的关系与类适配器相同,而Adapter与Adaptee之间的关系从“属于”变成了“包含”。这种关系下,Adapter需要保持一个对Adaptee的引用。在request方法中,Adapter发送[adaptee specficRequest]消息给引用adaptee,以间接访问它的行为,然后实现客户端请求的其余部分。由于Adapter与Adaptee之间是一种“包含”关系,用Adapter去适配Adaptee的子类也没什么问题
下面是简化代码(java):- Target接口的定义:
/**
* 定义客户端使用的接口,与特定领域相关
*/
public interface Target {
/**
* 示意方法,客户端请求处理的方法
*/
public void request();
}
- 再看看需要被适配的对象定义,示例代码如下
/**
* 已经存在的接口,这个接口需要被适配
*/
public class Adaptee {
/**
* 示意方法,原本已经存在,已经实现的方法
*/
public void specificRequest() {
//具体的功能处理
}
}
- 再看看适配器对象的基本实现,示例代码如下:
/**
* 适配器
*/
public class Adapter implements Target {
/**
* 持有需要被适配的接口对象
*/
private Adaptee adaptee;
/**
* 构造方法,传入需要被适配的对象
* @param adaptee 需要被适配的对象
*/
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
//可能转调已经实现了的方法,进行适配
adaptee.specificRequest();
}
}
- 再来看看使用适配器的客户端,示例代码如下:
/**
* 使用适配器的客户端
*/
public class Client {
public static void main(String[] args) {
// 创建需被适配的对象
Adaptee adaptee = new Adaptee();
// 创建客户端需要调用的接口对象
Target target = new Adapter(adaptee);
// 请求处理
target.request();
}
}
示例实现
我们有一个mediaPlayer接口和一个实现了mediaPlayer接口的实体类AudioPlayer.默认情况下,AudioPlayer可以播放MP3格式的音频文件.我们还有另一个接口AdvancedMediaPlayer和实现了AdvancedMediaPlayer接口的实体类,该类可以播放VLC和MP4格式的文件.
那么现在要求来了?我们想让AudioPlayer播放其他格式的音频文件.为了实现这个功能,我们需要实现MediaPlayer接口的适配器类MediaAdapter,并使用AdvancedMediaPlayer对象来播放所需的格式.
AudioPlayer使用适配器类MediaAdapter传递所需的音频类型,不需要知道能播放所需格式音频的实际类
步骤1
为媒体播放器和更高级的媒体播放器创建接口
typedef NS_OPTIONS(NSUInteger, AudioType){
AudioTypeMP3,
AudioTypeMP4,
AudioTypeVLC,
};
NS_ASSUME_NONNULL_BEGIN
@interface MediaPlayer : NSObject
- (void)play:(AudioType)type fileName:(NSString *)fileName;
@end
@interface AdvanceMediaPlayer : NSObject
- (void)playVlc:(NSString *)fileName;
- (void)playMp4:(NSString *)fileName;
@end
步骤2
创建实现了AdvanceMediaPlayer接口的实体类
@interface VlcPlayer : AdvanceMediaPlayer
@end
- (void)playVlc:(NSString *)fileName
{
NSLog(@"playing vlc file Name :%@", fileName);
}
@interface Mp4Player : AdvanceMediaPlayer
@end
- (void)playMp4:(NSString *)fileName
{
NSLog(@"playing mp4 file Name :%@", fileName);
}
步骤3
创建实现了MediaPlayer接口的适配器类(这儿通过继承实现,也就是上面说的类适配器)
@interface MediaAdapter : MediaPlayer
+ (instancetype)mediaAdapter:(AudioType)type;
@end
@interface MediaAdapter()
/**/
@property (nonatomic,strong) AdvanceMediaPlayer *advancedPlayer;
@end
@implementation MediaAdapter
+ (instancetype)mediaAdapter:(AudioType)type
{
MediaAdapter *adapter = [[MediaAdapter alloc] init];
if (type == AudioTypeMP4) {
adapter.advancedPlayer = [Mp4Player new];
} else if (type == AudioTypeVLC){
adapter.advancedPlayer = [VlcPlayer new];
}
return adapter;
}
- (void)play:(AudioType)type fileName:(NSString *)fileName
{
if (type == AudioTypeMP4) {
[_advancedPlayer playMp4:fileName];
} else if (type == AudioTypeVLC){
[_advancedPlayer playVlc:fileName];
}
}
@end
步骤4
创建实现了MediaPlayer接口的实体类
@interface AudioPlayer : MediaPlayer
@end
@interface AudioPlayer()
{
MediaAdapter *adapter;
}
@end
@implementation AudioPlayer
- (void)play:(AudioType)type fileName:(NSString *)fileName
{
if (type == AudioTypeMP3) {
NSLog(@"playing mp3 file , name:%@", fileName);
} else if (type == AudioTypeMP4 || type == AudioTypeVLC){
adapter = [MediaAdapter mediaAdapter:type];
[adapter play:type fileName:fileName];
}
}
@end
步骤5
使用 AudioPlayer 来播放不同类型的音频格式。
AudioPlayer *player = [[AudioPlayer alloc] init];
[player play:AudioTypeVLC fileName:@"123.vlc"];
[player play:AudioTypeMP4 fileName:@"你好.MP4"];
[player play:AudioTypeMP3 fileName:@"far.mp3"];
结果:
Demo地址
模式讲解
1. 认识适配器模式
- 模式的功能
适配器模式的主要功能是进行转换匹配,目的是复用已有的功能,而不是来实现新的接口.也就是说,客户端需要的功能应该是已经实现好了的,不需要适配器模式来实现,适配器模式主要负责把不兼容的接口转换成客户端期望的样子就好了.大家在看很多第三方库的版本兼容的时候很多都是用适配器去实现的
但这并不是说,在适配器里面就补鞥呢实现功能,适配器里面可以实现功能,称这种适配器为智能适配器。再说了,在接口匹配和转换的过程中,也是有可能需要额外实现一定的功能,才能够转换过来的,比如需要调整参数以进行匹配等。
- adaptee和Target的关系
适配器模式中呗适配的接口Adptee和适配称为的接口Target是没有关联的,也就是说,Adptee和Target中的方法既可以相同,也可以不同,极端情况下两个接口里面的方法可能是完全不同的,当然极端情况下也可以完全相同。
这里所说的相同和不同,是指的方法定义的名称、参数列表、返回值、包括方法本身的功能都可以相同和不同。
- 对象组合
根据前面的实现,你会发现,适配器的实现方式其实是依靠对象组合的方式.通过给适配器对象组合被适配的对象,然后当客户端调用target的时候,适配器会把相应的功能,委托给被适配的对象去完成
2. 适配器模式的实现
-
适配器的常见实现
在实现适配器的时候,适配器通常是一个类,一般会让适配器去实现Target接口,然后在适配器的具体实现里面调用Adaptee.也就是说适配器通常是一个Target类型,而不是Adaptee类型. -
智能适配器
在实际开发中,适配器也可以实现一些Adaptee没有实现,但是在Target中定义的功能,这种情况就需要在适配器的实现里面,加入新功能的实现,这种适配器被称为智能适配器。
如果要使用智能适配器,一般新加入的功能的实现,会用到很多Adaptee的功能,相当于利用Adaptee的功能来实现更高层的功能。当然也可以完全实现新加的功能,跟已有的功能都不靠边,变相是扩展了功能。 -
适配多个Adaptee
适配器在适配的时候,可以适配多个Adaptee,也就是说实现某个新的Target的功能的时候,需要调用到多个模块的功能,适配多个模块的功能才能满足新接口的要求。 -
适配器Adapter实现的复杂程度
适配器Adapter实现的复杂程度,取决于Target和Adaptee的相似程度。
如果相似程度很高,比如只有方法名称不一样,那么Adapter只是需要简单的转调一下接口就好了。
如果相似程度低,比如两边接口的方法定义的功能完全不一样,在Target中定义的一个方法,可能在Adaptee中定义了三个更小的方法,那么这个时候在实现Adapter的时候,就需要组合调用了。
总结
适配器模式说白了就是一种转换,比如iPhone6和其以后的耳机接口不一样,厂商就生产了一种转换接口,这个转换接口就是适配器