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

456 阅读4分钟

模式定义

适配器模式(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上可以下载,你可以戳这里,或者直接访问:github.com/FengHaiTong…