模式定义
适配器模式(Adapter Pattern)保持现有类功能不变,只是负责将一类接口转换为另一类开发者希望的接口。它还有另一个别名叫包装器(Wapper)。
需求场景
用现实案例来参照。适配器模式在现实生活中最典型的参照对象就是电源适配器。如美国和日本标准电压是110V,而中国的是220V,通过电源适配器就可以将当地电源转换成自己设备需要的电压。
这个案例非常符合适配器所要解决问题的特征:
- 我们需要使用现有的模块解决问题。(当地电源)
- 现有模块提供的接口不符合我们开发中的接口标准。(电压不一致)
- 我们设计了一个适配器,将接口标准改为我们需要的标准。(110V to 220V)
- 我们设计的适配器负责做接口转换,如非必要并不改动原有模块的功能。(电源还是那个电源)
开发案例
作为移动开发者,在开发中我们遇到的跟适配器有关的一个典型案例就是广告SDK
适配器。
在移动开发领域,开发者为了获取收益,往往会通过接广告平台的SDK
来获得广告收益。
广告平台有很多,比如谷歌的Admob,腾讯的优量汇,脸书的Audience Network
等。
对于开发者来说,如果想在应用中插入一个插屏广告,那么从开发者角度来讲,接口上来说仅仅是调用一个显示差评广告的接口即可,而无需关心该接口来自Admob
还是哪一家广告商。
然而,各大广告商提供的SDK
并不统一,如广告引擎初始化,弹起全屏广告等逻辑,都有各自的设计方案。这时候,我们就需要一个适配器,将各大平台的广告API
适配成我们自己需要的接口。
从案例出发,我们设计一个广告展示抽象类AbstractAdsAdapter
。
class AbstractAdsAdapter {
// 加载广告
func load() {}
// 显示广告
func showInterstitial() {}
}
通过抽象类,设计具体的子类AdmobAdsAdapter
。
class AdmobAdsAdapter: AbstractAdsAdapter {
override func initializeEngine() {
print("调用Admob初始化引擎接口")
}
override func load() {
print("调用Admob加载广告API的接口")
}
override func showInterstitial() {
print("调用Admob展示广告API的接口")
}
}
这样,Admob
的调用实现细节就通过AdmobAdsAdapter
来完成了。
如果我们还接了广点通的广告,那么就设计新的子类TencentAdsAdapter
。
class TencentAdsAdapter: AbstractAdsAdapter {
override func initializeEngine() {
print("调用广点通初始化引擎接口")
}
override func load() {
print("调用广点通加载广告API的接口")
}
override func showInterstitial() {
print("调用广点通展示广告API的接口")
}
}
接下来,为了完成广告模块的调用,我们提供了一个广告管理者类。
class AdsManager {
enum Engine: String {
case admob, tencent
}
private var map: [String: AbstractAdsAdapter] = [:]
func initialize(_ engine: Engine) {
var adapter: AbstractAdsAdapter?
switch engine {
case .admob :
adapter = AdmobAdsAdapter()
case .tencent :
adapter = TencentAdsAdapter()
}
if adapter != nil {
map[engine.rawValue] = adapter
adapter!.initializeEngine()
}
}
func load(engine: Engine) {
if let adapter = map[engine.rawValue] {
adapter.load()
} else {
print("广告引擎 \(engine.rawValue) 未初始化")
}
}
func showInterstitial(engine: Engine) {
if let adapter = map[engine.rawValue] {
adapter.showInterstitial()
} else {
print("广告引擎 \(engine.rawValue) 未初始化")
}
}
}
该管理者类提供了一个枚举类型Engine
,当我们指定对应的引擎时,管理者类就会调用对应的广告模块。
测试代码如下
class AdsManager {
enum Engine: String {
case admob, tencent
}
private var map: [String: AbstractAdsAdapter] = [:]
func initialize(_ engine: Engine) {
var adapter: AbstractAdsAdapter?
switch engine {
case .admob :
adapter = AdmobAdsAdapter()
case .tencent :
adapter = TencentAdsAdapter()
}
if adapter != nil {
map[engine.rawValue] = adapter
adapter!.initializeEngine()
}
}
func load(engine: Engine) {
if let adapter = map[engine.rawValue] {
adapter.load()
} else {
print("广告引擎 \(engine.rawValue) 未初始化")
}
}
func showInterstitial(engine: Engine) {
if let adapter = map[engine.rawValue] {
adapter.showInterstitial()
} else {
print("广告引擎 \(engine.rawValue) 未初始化")
}
}
}
广告管理者类通过一个容器map
存放已经初始化的广告适配器,然后同样提供和实现了加载和展示广告的接口。如果一个广告适配器未进行初始化,则不会放入到map
中,这时候调用其它接口将会导致失败。
调用代码测试。
let adsManager = AdsManager()
adsManager.initialize(.admob)
adsManager.load(engine: .admob)
adsManager.showInterstitial(engine: .admob)
adsManager.showInterstitial(engine: .tencent)
打印输出。
调用Admob初始化引擎接口
调用Admob加载广告API的接口
调用Admob展示广告API的接口
广告引擎 tencent 未初始化
广告适配器的示例代码到这里就差不多了,虽然实际项目开发中该框架还有不少要加入的,比如错误处理等。但是整个模型依然是建立在适配器的基础上。
以上示例代码在我的github
上可以下载,你可以戳这里,或者直接访问:https://github.com/FengHaiTongLuo/Swift-DesignPattern/blob/main/Adapter.swift 。