目录

闲话设计模式之适配器模式

闲话设计模式之适配器模式

模式定义

适配器模式(Adapter Pattern)保持现有类功能不变,只是负责将一类接口转换为另一类开发者希望的接口。它还有另一个别名叫包装器(Wapper)。

需求场景

用现实案例来参照。适配器模式在现实生活中最典型的参照对象就是电源适配器。如美国和日本标准电压是110V,而中国的是220V,通过电源适配器就可以将当地电源转换成自己设备需要的电压。

这个案例非常符合适配器所要解决问题的特征:

  1. 我们需要使用现有的模块解决问题。(当地电源)
  2. 现有模块提供的接口不符合我们开发中的接口标准。(电压不一致)
  3. 我们设计了一个适配器,将接口标准改为我们需要的标准。(110V to 220V)
  4. 我们设计的适配器负责做接口转换,如非必要并不改动原有模块的功能。(电源还是那个电源)

开发案例

作为移动开发者,在开发中我们遇到的跟适配器有关的一个典型案例就是广告 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 。