适配器模式是一种行为模式,允许不兼容的类型在一起工作。它涉及四个组成部分。
使用适配器的对象是依赖于新协议的对象。
新协议是所需的使用协议。
遗留对象在协议制定之前就已经存在,不能直接修改以符合协议。
4.创建一个符合协议的适配器,并将调用传递给传统对象。
当你考虑到最新的iPhone时,你会想到一个物理适配器的好例子--没有耳机插孔!如果你想把你的3.5英寸耳机插在上面,你就会发现你的耳机是不需要的。如果你想把你的3.5mm耳机插入闪电接口,你需要一个一端是闪电接口,另一端是3.5mm接口的适配器。
这基本上就是 "适配器模式 "的意义:将两个元素连接起来,否则就无法相互 "配合"。
类、模块和函数不能总是被修改,尤其是当它们来自第三方库时。有时候,你必须改编。
你可以通过扩展一个现有的类来创建一个适配器,或者创建一个新的适配器类。本章将告诉你如何做到这两点。
打开Starter目录下的IntermediateDesignPattern.xcworkspace,或者从上一章中你自己的playground工作空间继续,然后打开Adapter页面。
在这个例子中,你要改编一个第三方认证服务,使其与一个应用程序的内部认证协议一起工作。在代码示例后添加以下代码。
想象一下,GoogleAuthenticator是一个无法修改的第三方类。因此,它是旧对象。当然,实际的Google身份验证器会复杂得多。我们刚刚将这个命名为“Google”作为示例,并伪造了网络调用。
登录函数返回一个 GoogleUser,该用户具有名为 token 的字符串属性。你可以通过 GET 请求传递此令牌,如下所示:
• https://api.example.com/items/id123?token=special-token-value
或者,你可以通过持有者身份验证(例如 JSON Web 令牌)来使用它(请参阅 https://jwt.io/)。如果你不熟悉这些格式,没关系!它们不是本章所需的知识,而是简单地说明了常见的用例。
接下来,将以下代码添加到Playground的末尾:
这是你的应用程序的认证协议,作为新的协议。它需要一个电子邮件和密码。如果登录成功,它用一个用户和令牌调用成功。否则,它将调用失败,并给出一个错误。
应用程序将使用这个协议而不是直接使用GoogleAuthenticator,这样做可以获得很多好处。例如,你可以轻松地支持多种认证机制--Google、Facebook和其他--只需让它们都符合相同的协议。
虽然你可以扩展GoogleAuthenticator以使其符合AuthenticationService--这也是适配器模式的一种形式!但你也可以创建一个适配器类。- 你也可以创建一个Adapter类。将下面的代码添加到playground的末尾,以做到这一点。
你创建GoogleAuthenticationAdapter作为GoogleAuthenticationAdapter和AuthenticationService之间的适配器。
你声明一个对GoogleAuthenticator的私有引用,所以它对终端消费者是隐藏的。
根据协议的要求,你添加了AuthenticationService的登录方法。
在这个方法中,你调用Google的登录方法来获得一个GoogleUser。
如果有错误,你就用它来调用失败。
否则,你从googleUser中创建用户和令牌,并调用成功。通过像这样包装GoogleAuthenticator,终端消费者不需要直接与Google的API交互。这可以防止未来的变化。例如,如果谷歌改变了他们的API并破坏了你的应用程序,你只需要在一个地方进行修改:这个适配器。
将以下代码添加到playground的末尾。
你首先声明一个LoginViewController的新类。它有一个authService属性和e-mail和密码的文本字段。在一个真正的视图控制器中,你会在loadView中创建视图,或者将每个视图声明为@IBOutlet。为了简单起见,你把它们设置为新的UITextField实例。
你创建一个类方法,实例化LoginViewController并设置authService。
最后,你创建一个登录方法,用文本字段中的电子邮件和密码调用authService.login。
接下来,添加这段代码来试试。
在这里,你可以通过传递GoogleAuthenticatorAdapter作为authService来创建新的LoginViewController,为电子邮件和密码文本字段设置文本并调用登录。
你应该看到以下内容打印到控制台:
Auth succeeded: user@example.com, special-token-value
如果你想支持其他API,如Facebook login,你也可以很容易地为它们制作适配器,并让LoginViewController以完全相同的方式使用它们,而无需任何代码更改。
适配器模式允许你在不更改基础类型的情况下遵守新协议。这样做的后果是防止将来对基础类型进行更改,但也使你的实现更难阅读和维护。
在实现适配器模式时要小心,除非你认识到确实有更改的可能性。如果没有,请考虑直接使用基础类型是否有意义。
你将继续上一章的项目 Coffee Quest,并创建适配器类以将应用与 Yelp SDK 分离。
如果你跳过了上一章,或者想要重新开始,请打开 Finder 并导航到你下载本章资源的位置。然后,在 Xcode 中打开 starter\CoffeeQuest\CoffeeQuest.xcworkspace(未.xcodeproj)。
注意:如果你选择重新开始,则需要打开APIKeys.swift并添加Yelp API密钥。有关如何生成此模式的说明,请参阅第 10 章“模型-视图-视图模型模式”。
打开 ViewController.swift,你会看到这两个属性:
public var businesses: [YLPBusiness] = []
private let client = YLPClient(apiKey: YelpAPIKey)
因此,CoffeeQuest直接依赖于YLPBusiness和YLPClient,这是Yelp SDK提供的两个类。因此,该应用程序与Yelp SDK紧密耦合。
如果 SDK 曾经更改,则需要在多个位置更新应用。现在这不是一个大问题,因为该应用程序很小。但是,如果你继续开发应用并在许多地方直接使用 SDK,则以后可能会导致问题。
如果应用程序依赖于中间协议并且只在一个地方符合它,那就更好了。听起来很熟悉?这正是适配器模式的用途!
你将首先创建新的组和文件来组织新类型。
右键单击 CoffeeQuest 组,选择“新建组”并将其命名为“适配器”。重复此操作以创建名为“模型”的第二个组和名为“协议”的第三个组。
接下来,右键单击“适配器”,然后选择“新建文件...”。选择 iOS ▸ Swift 文件,然后单击下一步。将其称为 YLPClient+BusinessSearchClient.swift然后单击“创建”。
重复此过程以在模型下创建一个名为Business.swift的新字段,在BusinessSearchClient下.swift在协议下。
最后,右键单击CoffeeQuest,然后选择“按名称排序”。你的文件层次结构现在应如下所示:
你需要在协议和适配器中使用 Business,因此你将首先创建此类型。将 Business.swift 的内容替换为以下内容:
你将直接使用此模型而不是 YLPBusiness。通过这样做,你以后可以轻松地使用其他 API,例如 Google Places 或 MapKit,并将它们的输出映射到业务对象。
接下来,将 BusinessSearchClient.swift 的内容替换为以下内容:
你将使用此协议而不是 YLPSearch。
你将如何使用 YLPSearch?这就是适配器的用武之地。最后,将 YLPClient BusinessSearchClient.swift 的内容替换为以下内容:
这是它的工作原理:
扩展 YLPClient 以符合 BusinessSearchClient。这需要你实现搜索(with:term:limit:offset:success:failure:)。
要执行搜索,请将传入的 CLLocationCoordinate2D 转换为 YLPCoordinate 并在 YLPClient 上调用 search。
在completionHandler 中,你验证有一个searchResult 并且没有错误。否则,你使用错误调用失败。
在成功案例中,你在 searchResult.businesses 上调用 adaptToBusinesses() 以将 YLPBusiness 数组转换为 Business 数组。
扩展元素为 YLPBusiness 的数组。这允许你创建一个方便的方法来将 YLPBusiness 数组转换为 Business 数组。你现在已准备好实际使用 BusinessSearchClient 和 Business!打开 ViewController.swift 并替换这一行
private let client = YLPClient(apiKey: YelpAPIKey)
相反,现在忽略编译器错误:
public var client: BusinessSearchClient =
YLPClient(apiKey: YelpAPIKey)
这很微妙,但这里有三个显着的变化:
1.你将私有更改为公开。因此,如果你以后想要使用不同类型的 BusinessSearchClient,你将能够获得对视图控制器的引用并设置其客户端。
你将属性类型明确声明为 BusinessSearchClient。这确保编译器不会自动推断这是 YLPClient。
默认情况下,你将此属性设置为 YLPClient 的一个实例。
通过进行此更改,你实际上已将视图控制器与 YLPClient 分离!它现在依赖于 BusinessSearchClient,你可以轻松地将其替换为任何其他符合要求的类型。
接下来,替换以下行:
public var businesses: [YLPBusiness] = []
具有以下内容:
public var businesses: [Business] = []
这是与 Yelp SDK 脱钩的又一步。但是,你现在必须修复这些更改导致的编译器错误。
首先,将 searchForBusinesses() 的内容替换为以下内容:
这是它的工作原理:
你更新 callight 以使用在 BusinessSearchClient 上声明的方法,而不是来自 YLPClient 的方法。
如果搜索成功,将 self.businesses 设置为获取的商家。然后你分派到主队列以将注释添加到地图。
如果搜索失败,你只需将错误打印到控制台即可。
接下来需要更新 annotationFactory.createBusinessMapViewModel。此方法当前需要一个 YLPBusiness 作为输入。相反,你需要更改它以接受业务。
打开 AnnotationFactory.swift,并替换这些行:
用如下代码:
这里有三个小的变化:
你将输入参数的类型更改为业务。
你将返回类型更改为 BusinessMapViewModel 而不是可选的 BusinessMapViewModel?。
你从business.location 获取坐标,它不需要警卫,因为它不是可选类型。
你只需要进行最后一项更改即可使项目再次编译。打开 ViewController.swift,你会发现 addAnnoations() 中有一个编译器错误。
替换此代码:
使用如下代码:
let viewModel = annotationFactory.createBusinessMapViewModel(for: business)
这移除了保护,因为返回类型不再是可选的。
现在你已经调整了代码,构建并运行以确认一切都按预期工作。
你在本章中学习了适配器模式。以下是它的关键点:
• 适配器模式在处理来自无法修改的第三方库的类时很有用。你可以使用协议让它们与项目的自定义类一起工作。
• 要使用适配器,你可以扩展旧对象或创建新的适配器类。
• 适配器模式允许你重用一个类,即使它缺少必需的组件或具有与必需对象不兼容的组件。
• 在《时间简史》中,史蒂文·霍金说:“智慧是适应变化的能力。”也许他说的不是适配器模式,但这个想法是这个模式和许多其他模式的重要组成部分:为未来的变化提前计划。每次重构后,
Coffee Quest 都会变得更好!再一次,从视觉角度来看,应用程序并没有太大变化,但现在添加其他 API 并让它们与你的对象业务无缝协作会容易得多 。
在下一章中,你将了解迭代器模式。当前在视图控制器中迭代业务的机制并不理想。你将学习如何扩展类以使其可迭代且更易于管理。
上一章 | 目录 | 下一章 |
---|