第12章:适配器模式

适配器模式是一种行为模式,允许不兼容的类型在一起工作。它涉及四个组成部分。

  1. 使用适配器的对象是依赖于新协议的对象。

  2. 新协议是所需的使用协议。

  3. 遗留对象在协议制定之前就已经存在,不能直接修改以符合协议。

  4. 4.创建一个符合协议的适配器,并将调用传递给传统对象。

当你考虑到最新的iPhone时,你会想到一个物理适配器的好例子--没有耳机插孔!如果你想把你的3.5英寸耳机插在上面,你就会发现你的耳机是不需要的。如果你想把你的3.5mm耳机插入闪电接口,你需要一个一端是闪电接口,另一端是3.5mm接口的适配器。

这基本上就是 "适配器模式 "的意义:将两个元素连接起来,否则就无法相互 "配合"。

你什么时候应该使用它?

类、模块和函数不能总是被修改,尤其是当它们来自第三方库时。有时候,你必须改编。

你可以通过扩展一个现有的类来创建一个适配器,或者创建一个新的适配器类。本章将告诉你如何做到这两点。

Playground实例

打开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的末尾,以做到这一点。

下面是这个的作用。
  1. 你创建GoogleAuthenticationAdapter作为GoogleAuthenticationAdapter和AuthenticationService之间的适配器。

  2. 你声明一个对GoogleAuthenticator的私有引用,所以它对终端消费者是隐藏的。

  3. 根据协议的要求,你添加了AuthenticationService的登录方法。

在这个方法中,你调用Google的登录方法来获得一个GoogleUser。

  1. 如果有错误,你就用它来调用失败。

  2. 否则,你从googleUser中创建用户和令牌,并调用成功。通过像这样包装GoogleAuthenticator,终端消费者不需要直接与Google的API交互。这可以防止未来的变化。例如,如果谷歌改变了他们的API并破坏了你的应用程序,你只需要在一个地方进行修改:这个适配器。

将以下代码添加到playground的末尾。

以下是其工作方式。
  1. 你首先声明一个LoginViewController的新类。它有一个authService属性和e-mail和密码的文本字段。在一个真正的视图控制器中,你会在loadView中创建视图,或者将每个视图声明为@IBOutlet。为了简单起见,你把它们设置为新的UITextField实例。

  2. 你创建一个类方法,实例化LoginViewController并设置authService。

  3. 最后,你创建一个登录方法,用文本字段中的电子邮件和密码调用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 的内容替换为以下内容:

这是它的工作原理:

  1. 扩展 YLPClient 以符合 BusinessSearchClient。这需要你实现搜索(with:term:limit:offset:success:failure:)。

  2. 要执行搜索,请将传入的 CLLocationCoordinate2D 转换为 YLPCoordinate 并在 YLPClient 上调用 search。

  3. 在completionHandler 中,你验证有一个searchResult 并且没有错误。否则,你使用错误调用失败。

  4. 在成功案例中,你在 searchResult.businesses 上调用 adaptToBusinesses() 以将 YLPBusiness 数组转换为 Business 数组。

  5. 扩展元素为 YLPBusiness 的数组。这允许你创建一个方便的方法来将 YLPBusiness 数组转换为 Business 数组。你现在已准备好实际使用 BusinessSearchClient 和 Business!打开 ViewController.swift 并替换这一行

private let client = YLPClient(apiKey: YelpAPIKey)

相反,现在忽略编译器错误:

public var client: BusinessSearchClient = 
  YLPClient(apiKey: YelpAPIKey)

这很微妙,但这里有三个显着的变化:

1.你将私有更改为公开。因此,如果你以后想要使用不同类型的 BusinessSearchClient,你将能够获得对视图控制器的引用并设置其客户端。

  1. 你将属性类型明确声明为 BusinessSearchClient。这确保编译器不会自动推断这是 YLPClient。

  2. 默认情况下,你将此属性设置为 YLPClient 的一个实例。

通过进行此更改,你实际上已将视图控制器与 YLPClient 分离!它现在依赖于 BusinessSearchClient,你可以轻松地将其替换为任何其他符合要求的类型。

接下来,替换以下行:

public var businesses: [YLPBusiness] = []

具有以下内容:

public var businesses: [Business] = []

这是与 Yelp SDK 脱钩的又一步。但是,你现在必须修复这些更改导致的编译器错误。

首先,将 searchForBusinesses() 的内容替换为以下内容:

这是它的工作原理:

  1. 你更新 callight 以使用在 BusinessSearchClient 上声明的方法,而不是来自 YLPClient 的方法。

  2. 如果搜索成功,将 self.businesses 设置为获取的商家。然后你分派到主队列以将注释添加到地图。

  3. 如果搜索失败,你只需将错误打印到控制台即可。

接下来需要更新 annotationFactory.createBusinessMapViewModel。此方法当前需要一个 YLPBusiness 作为输入。相反,你需要更改它以接受业务。

打开 AnnotationFactory.swift,并替换这些行:


用如下代码:

这里有三个小的变化:

  1. 你将输入参数的类型更改为业务。

  2. 你将返回类型更改为 BusinessMapViewModel 而不是可选的 BusinessMapViewModel?。

  3. 你从business.location 获取坐标,它不需要警卫,因为它不是可选类型。

你只需要进行最后一项更改即可使项目再次编译。打开 ViewController.swift,你会发现 addAnnoations() 中有一个编译器错误。

替换此代码:

使用如下代码:

let viewModel = annotationFactory.createBusinessMapViewModel(for: business)

这移除了保护,因为返回类型不再是可选的。

现在你已经调整了代码,构建并运行以确认一切都按预期工作。

关键点

你在本章中学习了适配器模式。以下是它的关键点:

• 适配器模式在处理来自无法修改的第三方库的类时很有用。你可以使用协议让它们与项目的自定义类一起工作。

• 要使用适配器,你可以扩展旧对象或创建新的适配器类。

• 适配器模式允许你重用一个类,即使它缺少必需的组件或具有与必需对象不兼容的组件。

• 在《时间简史》中,史蒂文·霍金说:“智慧是适应变化的能力。”也许他说的不是适配器模式,但这个想法是这个模式和许多其他模式的重要组成部分:为未来的变化提前计划。每次重构后,

Coffee Quest 都会变得更好!再一次,从视觉角度来看,应用程序并没有太大变化,但现在添加其他 API 并让它们与你的对象业务无缝协作会容易得多 。

在下一章中,你将了解迭代器模式。当前在视图控制器中迭代业务的机制并不理想。你将学习如何扩展类以使其可迭代且更易于管理。


上一章 目录 下一章