iOS设计模式之适配器模式

266 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

  • 本文主要介绍iOS设计模式中的适配器模式,顾名思义,通过接口适配从而可以适用我们原有的流程。

1. 什么是适配器模式

已有的类与新的接口之间不兼容的问题相当普遍,人们为它找到了一个解决方案。这个解决方案广为使用,最终被编入设计模式,称为适配器
适配器模式,可以这么说,用于连接两种不同种类对象,使其毫无问题地协同工作。有时也称为“包装器”。其思想是非常简单,适配器实现客户端所需要的某种接口行为。同时,它又连接到另一个具有不同接口与行为的对象。一边是是客户端懂得如何使用的目标接口,另一边是客户端一无所知被适配者适配器站在2者之间。适配器的主要作用是把被适配着的行为传递另一端的客户端
举个例子如果你是第一次从美国到欧洲旅行, 那么在给电动剃须刀充电时可能会大吃一惊。 不同国家的电源插头和插座标准不同。 美国插头和德国插座不匹配。 同时提供美国标准插座和欧洲标准插头电源适配器可以解决你的难题。

适配器模式:将一个类的接口转换成客户希望的另一个接口。适配器模式是的原本由于接口不兼容而不能一起工作的那些类可以可以一起工作。

2.适配器模式结构

  • 对象适配器 实现时使用了构成原则: 适配器实现了其中一个对象的接口, 并对另一个对象进行封装。 所有流行的编程语言都可以实现适配器.

  • 类适配器 这一实现使用了继承机制: 适配器同时继承两个对象的接口。 请注意, 这种方式仅能在支持多重继承的编程语言中实现, 例如 C++。
    在iOS中我们通常会使用代理模式来适配,它属于对象适配器,而我们比如持有类对象的则属于类适配器

类适配器对象适配器
只针对单一具体Adaptee类,把Adaptee适配到Targe可以适配多个Adaptee及子类
易于重载Adaptee的行为,因为是通过直接的子类话进行适配难以重载Adaptee的行为,需要借助于子类的对象而不是Adaptee本身
只有一个Adaptee对象,无需额外的指针间接访问Adaptee需要额外的指针间接访问Adaptee 并适配其行为

解释下代表的含义:

Adaptee:被适配者
Target:目标接口
Adapter:适配器对象

3. 何时使用适配器模式

  • 已有类的接口与需求不匹配
  • 需要一个可复用的类,该类能够同可能带有不兼容接口的其他类协作
  • 需要适配一个类的几个不同子类,可是让每一个子类区子类化一个类适配器又不现实。那么可以使用对象适配器适配其父类的接口

4. 代码展示

  • 类适配器 这里就是我们适配器Adapter持有Adaptee被适配者 ,子类化的类适配器。
import XCTest

/// The Target defines the domain-specific interface used by the client code.
class Target {

    func request() -> String {
        return "Target: The default target's behavior."
    }
}

/// The Adaptee contains some useful behavior, but its interface is incompatible
/// with the existing client code. The Adaptee needs some adaptation before the
/// client code can use it.
class Adaptee {

    public func specificRequest() -> String {
        return ".eetpadA eht fo roivaheb laicepS"
    }
}

/// The Adapter makes the Adaptee's interface compatible with the Target's
/// interface.
class Adapter: Target {

    private var adaptee: Adaptee

    init(_ adaptee: Adaptee) {
        self.adaptee = adaptee
    }

    override func request() -> String {
        return "Adapter: (TRANSLATED) " + adaptee.specificRequest().reversed()
    }
}

/// The client code supports all classes that follow the Target interface.
class Client {
    // ...
    static func someClientCode(target: Target) {
        print(target.request())
    }
    // ...
}

/// Let's see how it all works together.
class AdapterConceptual: XCTestCase {

    func testAdapterConceptual() {
        print("Client: I can work just fine with the Target objects:")
        Client.someClientCode(target: Target())

        let adaptee = Adaptee()
        print("Client: The Adaptee class has a weird interface. See, I don't understand it:")
        print("Adaptee: " + adaptee.specificRequest())

        print("Client: But I can work with it via the Adapter:")
        Client.someClientCode(target: Adapter(adaptee))
    }

执行结果

Client: I can work just fine with the Target objects:
Target: The default target's behavior.
Client: The Adaptee class has a weird interface. See, I don't understand it:
Adaptee: .eetpadA eht fo roivaheb laicepS
Client: But I can work with it via the Adapter:
Adapter: (TRANSLATED) Special behavior of the Adaptee.
  • 对象适配器 采用代理的方式进行,扩容接口,被适配者遵循协议并实现它,适配器调用协议。
import XCTest
import UIKit

/// Adapter Design Pattern
///
/// Intent: Convert the interface of a class into the interface clients expect.
/// Adapter lets classes work together that couldn't work otherwise because of
/// incompatible interfaces.

class AdapterRealWorld: XCTestCase {

    /// Example. Let's assume that our app perfectly works with Facebook
    /// authorization. However, users ask you to add sign in via Twitter.
    ///
    /// Unfortunately, Twitter SDK has a different authorization method.
    ///
    /// Firstly, you have to create the new protocol 'AuthService' and insert
    /// the authorization method of Facebook SDK.
    ///
    /// Secondly, write an extension for Twitter SDK and implement methods of
    /// AuthService protocol, just a simple redirect.
    ///
    /// Thirdly, write an extension for Facebook SDK. You should not write any
    /// code at this point as methods already implemented by Facebook SDK.
    ///
    /// It just tells a compiler that both SDKs have the same interface.

    func testAdapterRealWorld() {

        print("Starting an authorization via Facebook")
        startAuthorization(with: FacebookAuthSDK())

        print("Starting an authorization via Twitter.")
        startAuthorization(with: TwitterAuthSDK())
    }

    func startAuthorization(with service: AuthService) {

        /// The current top view controller of the app
        let topViewController = UIViewController()

        service.presentAuthFlow(from: topViewController)
    }
}

protocol AuthService {

    func presentAuthFlow(from viewController: UIViewController)
}

class FacebookAuthSDK {

    func presentAuthFlow(from viewController: UIViewController) {
        /// Call SDK methods and pass a view controller
        print("Facebook WebView has been shown.")
    }
}

class TwitterAuthSDK {

    func startAuthorization(with viewController: UIViewController) {
        /// Call SDK methods and pass a view controller
        print("Twitter WebView has been shown. Users will be happy :)")
    }
}

extension TwitterAuthSDK: AuthService {

    /// This is an adapter
    ///
    /// Yeah, we are able to not create another class and just extend an
    /// existing one

    func presentAuthFlow(from viewController: UIViewController) {
        print("The Adapter is called! Redirecting to the original method...")
        self.startAuthorization(with: viewController)
    }
}

extension FacebookAuthSDK: AuthService {
    /// This extension just tells a compiler that both SDKs have the same
    /// interface.
}

执行结果

Starting an authorization via Facebook
Facebook WebView has been shown
///
Starting an authorization via Twitter
The Adapter is called! Redirecting to the original method...
Twitter WebView has been shown. Users will be happy :)

5. 总结

代理模式本身可以达到多种目的,不只有适配器模式。我们也可以把适配器定义为Object—C中的Block,或者Swift中的闭包达到同样的效果,而且不会存在继承的问题。本质上来说适配器就是一种方法的转换,或者说方法的拓展,为了更好的适用性。比如我们表达颜色的时候可以使用RGB的形式,也可以直接使用Color输入。