iOS.& Swift Books 教程设计模式

12
适配器模式 杰伊突发撰写

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

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

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

  3. A 遗产对象 在制作协议之前存在并且不能直接修改以符合其。

  4. 一个 适配器 创建以符合协议,并将呼叫传递到传统对象上。

一个伟大的例子 身体的 当您考虑最新的iPhone时,适配器就会想到 - 没有耳机杰克!如果要将3.5mm耳机插入闪电口,您需要一个带有闪电连接器的适配器,另一端和另一块3.5mm插孔。

这基本上是适配器图案是关于的:连接两个元素,否则彼此不会“适合”。

你什么时候应该用它?

类,模块和功能不能始终修改,特别是如果它们来自第三方库。有时你必须适应!

您可以通过扩展现有类或创建新的适配器类来创建适配器。本章将向您展示如何做两者。

操场例子

打开 中级agepattern.xcWorkspace. 在里面 起动机 目录,或从最后一章中从您自己的游乐场工作区继续,然后打开 适配器 page.

import UIKit

// MARK: - Legacy Object
public  class GoogleAuthenticator {
  public func login(
    email: String,
    password: String,
    completion: @escaping (GoogleUser?, Error?) -> Void) {
    
    // Make networking calls that return a token string
    let token = "special-token-value"
    
    let user = GoogleUser(email: email,
                          password: password,
                          token: token)
    completion(user, nil)
  }
}

public struct GoogleUser {
  public var email: String
  public var password: String
  public var token: String
}
// MARK: - New Protocol
public protocol AuthenticationService {
  func login(email: String,
             password: String,
             success: @escaping (User, Token) -> Void,
             failure: @escaping (Error?) -> Void)
}

public struct User {
  public let email: String
  public let password: String
}

public struct Token {
  public let value: String
}
// MARK: - Adapter
// 1
public class GoogleAuthenticatorAdapter: AuthenticationService {
  
  // 2
  private var authenticator = GoogleAuthenticator()
  
  // 3
  public func login(email: String,
                    password: String,
                    success: @escaping (User, Token) -> Void,
                    failure: @escaping (Error?) -> Void) {
    
    authenticator.login(email: email, password: password) { 
      (googleUser, error) in

      // 4
      guard let googleUser = googleUser else {
        failure(error)
        return
      }
      
      // 5
      let user = User(email: googleUser.email,
                      password: googleUser.password)

      let token = Token(value: googleUser.token)
      success(user, token)
    }
  }
}
// MARK: - Object Using an Adapter
// 1
public class LoginViewController: UIViewController {
  
  // MARK: - Properties
  public var authService: AuthenticationService!
  
  // MARK: - Views
  var emailTextField = UITextField()
  var passwordTextField = UITextField()
  
  // MARK: - Class Constructors
  // 2
  public class func instance(
    with authService: AuthenticationService)
      -> LoginViewController {
      let viewController = LoginViewController()
      viewController.authService = authService
      return viewController
  }
  
  // 3
  public func login() {
    guard let email = emailTextField.text,
      let password = passwordTextField.text else {
        print("Email and password are required inputs!")
        return
    }
    authService.login(
      email: email,
      password: password,
      success: { user, token in
        print("Auth succeeded: \(user.email), \(token.value)")
    },
      failure: { error in
        print("Auth failed with error: no error provided")
    })
  }
}
// MARK: - Example
let viewController = LoginViewController.instance(
  with: GoogleAuthenticatorAdapter())
viewController.emailTextField.text = "[email protected]"
viewController.passwordTextField.text = "password"
viewController.login()
Auth succeeded: [email protected], special-token-value

你应该小心吗?

适配器模式允许您符合新协议而不更改底层类型。这使得防止对底层类型的未来变化的结果,但它还使您的实施更加努力阅读和维护。

教程项目

您将继续上一章的项目,咖啡任务,并创建适配器类来从Yelp SDK中解耦应用程序。

public var businesses: [YLPBusiness] = []
private let client = YLPClient(apiKey: YelpAPIKey)

import MapKit

public struct Business {
  var name: String
  var rating: Double
  var location: CLLocationCoordinate2D
}
import MapKit

public protocol BusinessSearchClient {
  func search(with coordinate: CLLocationCoordinate2D,
              term: String,
              limit: UInt,
              offset: UInt,
              success: @escaping (([Business]) -> Void),
              failure: @escaping ((Error?) -> Void))
}
import MapKit
import YelpAPI

// 1
extension YLPClient: BusinessSearchClient {
    
  public func search(with coordinate: CLLocationCoordinate2D,
                     term: String,
                     limit: UInt,
                     offset: UInt,
                     success: @escaping (([Business]) -> Void),
                     failure: @escaping ((Error?) -> Void)) {
    
    // 2
    let yelpCoordinate = YLPCoordinate(
      latitude: coordinate.latitude,
      longitude: coordinate.longitude)
      
    search(
      with: yelpCoordinate,
      term: term,
      limit: limit,
      offset: offset,
      sort: .bestMatched,
      completionHandler: { (searchResult, error) in
        
        // 3
        guard let searchResult = searchResult,
          error == nil else {
          failure(error)
          return
        }
        
        // 4
        let businesses =
          searchResult.businesses.adaptToBusinesses()
        success(businesses)
    })
  }
}

// 5
extension Array where Element: YLPBusiness {

  func adaptToBusinesses() -> [Business] {
  
    return compactMap { yelpBusiness in
      guard let yelpCoordinate =
        yelpBusiness.location.coordinate else {
        return nil
      }
      let coordinate = CLLocationCoordinate2D(
        latitude: yelpCoordinate.latitude,
        longitude: yelpCoordinate.longitude)
        
      return Business(name: yelpBusiness.name,
                      rating: yelpBusiness.rating,
                      location: coordinate)
    }
  }
}
private let client = YLPClient(apiKey: YelpAPIKey)
public var client: BusinessSearchClient = 
  YLPClient(apiKey: YelpAPIKey)
public var businesses: [YLPBusiness] = []
public var businesses: [Business] = []
// 1
client.search(
  with: mapView.userLocation.coordinate,
  term: "coffee",
  limit: 35, offset: 0,
  success: { [weak self] businesses in
    guard let self = self else { return }
    
    // 2
    self.businesses = businesses
    DispatchQueue.main.async {
      self.addAnnotations()
    }
  }, failure: { error in
  
    // 3
    print(" 搜索  failed: \(String(describing: error))")
})
public func createBusinessMapViewModel(
  for business: YLPBusiness) -> BusinessMapViewModel? {
  guard let yelpCoordinate =
    business.location.coordinate else {
    return nil
  }

  let coordinate = CLLocationCoordinate2D(
    latitude: yelpCoordinate.latitude,
    longitude: yelpCoordinate.longitude)
  public func createBusinessMapViewModel(
    for business: Business) -> BusinessMapViewModel {
    
    let coordinate = business.location
guard let viewModel = 
  annotationFactory.createBusinessMapViewModel(for: business) 
  else {
      continue
  }
let viewModel = 
  annotationFactory.createBusinessMapViewModel(for: business)

关键点

您了解了本章中的适配器模式。以下是其关键点:

有一个技术问题?想报告一个错误吗? 您可以向官方书籍论坛中的书籍作者提出问题和报告错误 这里 .

有反馈分享在线阅读体验吗? 如果您有关于UI,UX,突出显示或我们在线阅读器的其他功能的反馈,您可以将其发送到设计团队,其中表格如下所示:

© 2021 Razeware LLC

您可以免费读取,本章的部分显示为 混淆了 文本。解锁这本书,以及我们整个书籍和视频目录,带有Raywenderlich.com的专业订阅。

现在解锁

要突出或记笔记,您需要在订阅中拥有这本书或自行购买。