模式定義
適配器模式(Adapter Pattern)保持現(xiàn)有類功能不變,只是負(fù)責(zé)將一類接口轉(zhuǎn)換為另一類開發(fā)者希望的接口。它還有另一個(gè)別名叫包裝器(Wapper)。
需求場(chǎng)景
用現(xiàn)實(shí)案例來(lái)參照。適配器模式在現(xiàn)實(shí)生活中最典型的參照對(duì)象就是電源適配器。如美國(guó)和日本標(biāo)準(zhǔn)電壓是110V,而中國(guó)的是220V,通過(guò)電源適配器就可以將當(dāng)?shù)仉娫崔D(zhuǎn)換成自己設(shè)備需要的電壓。
這個(gè)案例非常符合適配器所要解決問(wèn)題的特征:
- 我們需要使用現(xiàn)有的模塊解決問(wèn)題。(當(dāng)?shù)仉娫矗?/li>
- 現(xiàn)有模塊提供的接口不符合我們開發(fā)中的接口標(biāo)準(zhǔn)。(電壓不一致)
- 我們?cè)O(shè)計(jì)了一個(gè)適配器,將接口標(biāo)準(zhǔn)改為我們需要的標(biāo)準(zhǔn)。(110V to 220V)
- 我們?cè)O(shè)計(jì)的適配器負(fù)責(zé)做接口轉(zhuǎn)換,如非必要并不改動(dòng)原有模塊的功能。(電源還是那個(gè)電源)
開發(fā)案例
作為移動(dòng)開發(fā)者,在開發(fā)中我們遇到的跟適配器有關(guān)的一個(gè)典型案例就是廣告SDK適配器。
在移動(dòng)開發(fā)領(lǐng)域,開發(fā)者為了獲取收益,往往會(huì)通過(guò)接廣告平臺(tái)的SDK來(lái)獲得廣告收益。
廣告平臺(tái)有很多,比如谷歌的Admob,騰訊的優(yōu)量匯,臉書的Audience Network等。
對(duì)于開發(fā)者來(lái)說(shuō),如果想在應(yīng)用中插入一個(gè)插屏廣告,那么從開發(fā)者角度來(lái)講,接口上來(lái)說(shuō)僅僅是調(diào)用一個(gè)顯示插屏廣告的接口即可,而無(wú)需關(guān)心該接口來(lái)自Admob還是哪一家廣告商。
然而,各大廣告商提供的SDK并不統(tǒng)一,如廣告引擎初始化,彈起全屏廣告等邏輯,都有各自的設(shè)計(jì)方案。這時(shí)候,我們就需要一個(gè)適配器,將各大平臺(tái)的廣告API適配成我們自己需要的接口。
從案例出發(fā),我們?cè)O(shè)計(jì)一個(gè)廣告展示抽象類AbstractAdsAdapter。
class AbstractAdsAdapter {
// 加載廣告
func load() {}
// 顯示廣告
func showInterstitial() {}
}
通過(guò)抽象類,設(shè)計(jì)具體的子類AdmobAdsAdapter。
class AdmobAdsAdapter: AbstractAdsAdapter {
override func initializeEngine() {
print("調(diào)用Admob初始化引擎接口")
}
override func load() {
print("調(diào)用Admob加載廣告API的接口")
}
override func showInterstitial() {
print("調(diào)用Admob展示廣告API的接口")
}
}
這樣,Admob的調(diào)用實(shí)現(xiàn)細(xì)節(jié)就通過(guò)AdmobAdsAdapter來(lái)完成了。
如果我們還接了廣點(diǎn)通的廣告,那么就設(shè)計(jì)新的子類TencentAdsAdapter。
class TencentAdsAdapter: AbstractAdsAdapter {
override func initializeEngine() {
print("調(diào)用廣點(diǎn)通初始化引擎接口")
}
override func load() {
print("調(diào)用廣點(diǎn)通加載廣告API的接口")
}
override func showInterstitial() {
print("調(diào)用廣點(diǎn)通展示廣告API的接口")
}
}
接下來(lái),為了完成廣告模塊的調(diào)用,我們提供了一個(gè)廣告管理者類。
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) 未初始化")
}
}
}
該管理者類提供了一個(gè)枚舉類型Engine,當(dāng)我們指定對(duì)應(yīng)的引擎時(shí),管理者類就會(huì)調(diào)用對(duì)應(yīng)的廣告模塊。
測(cè)試代碼如下
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) 未初始化")
}
}
}
廣告管理者類通過(guò)一個(gè)容器map存放已經(jīng)初始化的廣告適配器,然后同樣提供和實(shí)現(xiàn)了加載和展示廣告的接口。如果一個(gè)廣告適配器未進(jìn)行初始化,則不會(huì)放入到map中,這時(shí)候調(diào)用其它接口將會(huì)導(dǎo)致失敗。
調(diào)用代碼測(cè)試。
let adsManager = AdsManager()
adsManager.initialize(.admob)
adsManager.load(engine: .admob)
adsManager.showInterstitial(engine: .admob)
adsManager.showInterstitial(engine: .tencent)
打印輸出。
調(diào)用Admob初始化引擎接口
調(diào)用Admob加載廣告API的接口
調(diào)用Admob展示廣告API的接口
廣告引擎 tencent 未初始化
廣告適配器的示例代碼到這里就差不多了,雖然實(shí)際項(xiàng)目開發(fā)中該框架還有不少要加入的,比如錯(cuò)誤處理等。但是整個(gè)模型依然是建立在適配器的基礎(chǔ)上。
以上示例代碼在我的github上可以下載,你可以戳這里,或者直接訪問(wèn):https://github.com/FengHaiTongLuo/Swift-DesignPattern/blob/main/Adapter.swift 。