“幸存下来的不是最强壮的物种,也不是最聪明的。它是最能适应变化的一种。” -查尔斯·达尔文
作为人类,我们知道我们必须适应不同的环境条件。我们被认为是最擅长的,因此我们处于食物链的顶端。同样的能力也适用于我们的代码。如果我们希望我们的应用程序持续更长时间,它应该能够适应。
适配器模式是一种行为设计模式,它允许不兼容的类进行协作。主要是一个旧的遗留类和一个新的现代类。
在其他来源中,它也被定义为结构设计模式,因为这种模式结合了两个独立接口的功能。
它通常由四部分组成:
- 使用适配器的对象是符合适配器协议的对象。
- 适配器协议规定了新代码应该使用什么来适应遗留类。
- 开发了一个适配器来将参数和调用传递给遗留对象。
- 在创建协议之前存在的遗留类,不能直接更改以遵守它。
可以通过扩展现有类或构造新的适配器类来创建适配器。
我们什么时候使用它?
这“并不总是能够改变类,模块或功能,特别是如果他们从第三方库是。
最常用的比喻是电源适配器。美国和欧盟为其电气设备使用不同的插座。那么作为居住在美国的人,当我们访问欧盟时需要为手机充电时,我们该怎么办?我们随身携带一个适配器。使我们的国外充电器可以安全地与当地的电源插座通信。
一个典型的用例是第三方登录身份验证服务,例如 Apple、Google 或 Facebook。我们绝对不想为三个不同的提供者处理三种不同的情况。这就是我们创建一个适配器并使用适当的适配器类处理每个提供程序的原因。这样我们的视图控制器只能与一个界面而不是三个界面交互。
另一个示例是移动应用程序中的事件服务。我们的应用程序可能有第三方事件提供程序。我们不想仅仅依赖他们提供的功能。我们希望能够控制失败的情况。可能存在第三方库发生变化的情况,我们不想独自修改应用程序上的每个事件。这就是为什么我们在它们的功能和我们的视图控制器之间放置了一个适配器。
执行
闲话少说,让我们用 Xcode Playground 做一个例子。创建一个新的 Playground 项目并执行。
对于此示例,我们将调整第 3 方身份验证服务。
import UIKit
// MARK: AuthenticatorUser Model
public struct AuthenticatorUser {
public var email: String
public var password: String
public var token: String
}
// MARK: ThirdPartyAuthenticator service's implementation
public class ThirdPartyAuthenticator {
public func login(email: String,
password: String,
completion: @escaping (AuthenticatorUser?, Error?) -> Void) {
/// Some logic and network call that will provide us a user for an email and password goes here...
/// Finally, a token is given. Generally this is provided to us so that we make some requests using it.
let token = "64236324.375928.23934324"
/// Create a user instance using the information we have so far.
let user = AuthenticatorUser(email: email,
password: password,
token: token)
/// Pass the user object to the completion handler.
completion(user, nil)
}
}
ImagineThirdPartyAuthenticator是一个无法修改的第三方类。因此,它是遗留对象。为简单起见,我们直接提供了令牌,并假装网络调用没有出错。预计这个过程在现实生活中的图书馆中会更加复杂和离散。
最终登录函数返回 aUser和 an Error。
接下来,将以下代码添加到 Playground 的末尾:
// MARK: Our app's User and Token models
public struct User {
public let email: String
public let password: String
}
public struct Token {
public let value: String
}
// MARK: - New Protocol
public protocol AuthenticationServiceProtocol {
func login(email: String,
password: String,
success: @escaping (User, Token) -> Void,
failure: @escaping (Error?) -> Void)
}
这是我们应用程序的身份验证服务协议,充当适配器协议。它需要一个电子邮件和密码。如果登录成功,它会success用 aUser和调用Token。否则,它将failure使用Error.
我们的应用程序将使用该协议而不是ThirdPartyAuthenticator直接使用,并通过这样做获得影响力。例如,我们将能够支持多种身份验证机制——Apple、Facebook 和其他——只需让它们都符合相同的协议。这是这种设计模式的基础。这不是很聪明吗?
接下来,添加以下代码:
// MARK: - ThirdPartyAuthenticatorAdapter class
public class ThirdPartyAuthenticatorAdapter: AuthenticationServiceProtocol {
/// We create an instance of the ThirdPartyAuthenticator
private var authenticator = ThirdPartyAuthenticator()
/// This function come from the AuthenticationServiceProtocol, we have to conform to it.
public func login(email: String,
password: String,
success: @escaping (User, Token) -> Void,
failure: @escaping (Error?) -> Void) {
/// The authenticator takes the email and the password arguments passed by login function and uses them to provide an AuthenticatorUser or an error.
authenticator.login(email: email, password: password) { (authenticatorUser, error) in
/// We first make sure that the user is not nil. Otherwise we have to fail safely.
guard let authenticatorUser = authenticatorUser else {
failure(error)
return
}
/// If the authenticatorUser is not nil, we can finally create a User instance by using authenticatorUser properties that provided to us.
let user = User(email: authenticatorUser.email,
password: authenticatorUser.password)
let token = Token(value: authenticatorUser.token)
/// Finally we pass the user and token objects to the completin handler.
success(user, token)
}
}
}
我们创建ThirdPartyAuthenticationAdapter了适配器并使其符合AuthenticationServiceProtocol. 然后我们创建了一个ThirdPartyAuthenticator. 它是私有的,因此它对其他类是隐藏的。
我们AuthenticationServiceProtocol根据协议的要求添加了登录方法。在此方法中,我们调用第三方库登录方法来获取AuthenticatorUser. 如果有错误,我们会调用failure它。否则,我们创建userandtoken从authenticatorUserand 调用success。
在ThirdPartyAuthenticator现在被封闭在转接器内,使最终消费者并不需要直接与ThirdPartyAuthenticator的API进行交互。这可以防止未来的变化和可能的故障。例如,如果第三方身份验证器更改了他们的 API,它将破坏应用程序。但是对于适配器类,我们只需要在这里修复它。
继续添加以下代码:
// MARK: - The object using the Adapter
// This LoginViewController will handle the login UI
public class LoginViewController: UIViewController {
// MARK: - Properties
public var authService: AuthenticationServiceProtocol!
// MARK: - Views
var emailTextField = UITextField()
var passwordTextField = UITextField()
// To initialize this login view controller, we have to provide an authentication service that conforms to the AuthenticationServiceProtocol
public class func instance( with authService: AuthenticationServiceProtocol) -> LoginViewController {
let viewController = LoginViewController()
viewController.authService = authService
return viewController
}
// Login view controller's login function that does the real job. Notice that it doesn't know anything about ThirdPartyAuthenticator.
public func login() {
guard let email = emailTextField.text,
let password = passwordTextField.text else {
print("Email and password are required!")
return
}
authService.login(email: email,
password: password,
success: { user, token in
print("Login successful.\nEmail: \(user.email) \nToken: \(token.value)")
}, failure: { error in
print("Login failed with error.")
}
)
}
}
对于 LoginViewController,我们首先创建了一个新类。它有一个 authService 属性以及电子邮件和密码文本字段。我们通常会在 viewDidLoad 中构建视图,或者在实际的视图控制器中将每个视图定义为@IBOutlet。
之后,我们创建了一个创建 LoginViewController 并设置 authService 的类方法。它采用符合 AuthenticatorAdapterProtocol 的任何类型。
最后,我们调用了创建的登录函数并使用了 authService 的登录方法,这样我们就可以使用在文本字段中输入的电子邮件地址和密码登录。
关键时刻,让我们来测试一下
写下下面的代码。
// MARK: - Let's try it out
let viewController = LoginViewController.instance(with: ThirdPartyAuthenticatorAdapter())
viewController.emailTextField.text = "kimyon@catmail.com"
viewController.passwordTextField.text = "TunaF1sh"
viewController.login()
我们应该在控制台上看到下面的输出:
登陆成功。
邮箱:kimyon@catmail.com :64236324.375928.23934324
我们可以为新的 API 开发适配器,例如 Facebook 登录,并让 LoginViewController 以相同的方式使用它们,而无需更改任何代码。
当…时要小心
您可以使用适配器模式来符合新协议,而无需更改底层类型。这可以保护您免受未来对基础类型的更改,但也会使您的实现更难以理解和维护。所以记录你的适配器。添加说明和评论。
如果您打算使用适配器模式,请确保您了解更改的可能性。如果没有,请评估直接使用对象是否更有意义。
这是一个很好的学习和实施模式,因为它几乎用于市场上的每个应用程序。这将是您的编码库的重要资产。
我希望上面的解释是有道理的,并且对你的 iOS 开发之旅有所帮助。
精品教程推荐
加入我们一起学习SwiftUI
QQ:3365059189
SwiftUI技术交流QQ群:518696470
教程网站:www.openswiftui.com