iOS.& Swift Books 教程设计模式

22
连锁责任模式 由约书亚格林写

责任链模式是一种行为设计模式,允许由许多处理程序中的一个处理的事件。它涉及三种类型:

  1. 客户 接受并将事件传递给处理程序协议的实例。事件可能是简单的,只属性的结构或复杂对象,例如错综复杂的用户操作。

  2. 处理程序协议 定义具体处理程序必须实现的必需属性和方法。这可以代替抽象的基类,而是允许在其上存储存储属性。即便如此,它仍然没有意图直接实例化。相反,它只定义了混凝土处理程序必须符合的要求。

  3. 首先 具体处理器 实现处理程序协议,它直接由客户端存储。收到事件后,它首先尝试处理它。如果它无法这样做,它将事件传递给它 下一个 handler.

因此,客户端可以将所有混凝土处理程序视为单个实例。在引擎盖下,每个具体处理程序确定是否处理传递给它的事件或将其传递到下一个处理程序。如果没有客户需要了解该过程的任何事情,就会发生这种情况!

If there aren’t any concrete handlers capable of handling the event, the last handler simply returns nil, does nothing or throws an error depending on your requirements.

你什么时候应该用它?

只要您拥有处理类似事件的一组相关对象,而且根据事件类型,属性或与事件相关的其他任何其他方式都不同,请使用此模式。

混凝土处理程序可能完全是不同的类,或者它们可能是相同的类类型但不同的实例和配置。

For example, you can use this pattern to implement a VendingMachine that accepts coins:

  • VendingMachine itself would be the client and would accept coin input events.
  • 这 handler protocol would require a handleCoinValidation(_:) method and a 下一个 property.
  • 这 concrete handlers would be coin validators. They would determine whether an unknown coin was valid based on certain criteria, such as a coin’s weight and diameter, and use this to create a known coin type, such as a Penny.

操场例子

打开 AdvancedDesignPatterns.xcWorkspace. 在里面 起动机 目录,然后打开 ChainofRespOrsibility page.

// MARK: - Models
// 1
public class Coin {

  // 2
  public class var standardDiameter: Double {
    return 0
  }
  public class var standardWeight: Double {
    return 0
  }

  // 3
  public var centValue: Int { return 0 }
  public final var dollarValue: Double {
    return Double(centValue) / 100
  }

  // 4
  public final let diameter: Double
  public final let weight: Double

  // 5
  public required init(diameter: Double, weight: Double) {
    self.diameter = diameter
    self.weight = weight
  }

  // 6
  public convenience init() {
    let diameter = type(of: self).standardDiameter
    let weight = type(of: self).standardWeight
    self.init(diameter: diameter, weight: weight)
  }
}
extension Coin: CustomStringConvertible {
  public var description: String {
    return String(format:
    "%@ {diameter: %0.3f, dollarValue: $%0.2f, weight: %0.3f}",
    "\(type(of: self))", diameter, dollarValue, weight)
  }
}
public class Penny: Coin {

  public override class var standardDiameter: Double {
    return 19.05
  }
  public override class var standardWeight: Double {
    return 2.5
  }
  public override var centValue: Int { return 1 }
}

public class Nickel: Coin {

  public override class var standardDiameter: Double {
    return 21.21
  }
  public override class var standardWeight: Double {
    return 5.0
  }
  public override  var centValue: Int { return 5 }
}

public class Dime: Coin {
  public override class var standardDiameter: Double {
    return 17.91
  }
  public override class var standardWeight: Double {
    return 2.268
  }
  public override  var centValue: Int { return 10 }
}

public class Quarter: Coin {

  public override class var standardDiameter: Double {
    return 24.26
  }
  public override class var standardWeight: Double {
    return 5.670
  }
  public override  var centValue: Int { return 25 }
}
// MARK: - HandlerProtocol
public protocol CoinHandlerProtocol {
  var next: CoinHandlerProtocol? { get }
  func handleCoinValidation(_ unknownCoin: Coin) -> Coin?
}
// MARK: - Concrete Handler
// 1
public class CoinHandler {

  // 2
  public var next: CoinHandlerProtocol?
  public let coinType: Coin.Type
  public let diameterRange: ClosedRange<Double>
  public let weightRange: ClosedRange<Double>

  // 3
  public init(coinType: Coin.Type,
              diameterVariation: Double = 0.05,
              weightVariation: Double = 0.05) {
    self.coinType = coinType

    let standardDiameter = coinType.standardDiameter
    self.diameterRange =
      (1-diameterVariation)*standardDiameter ...
      (1+diameterVariation)*standardDiameter

    let standardWeight = coinType.standardWeight
    self.weightRange =
      (1-weightVariation)*standardWeight ...
      (1+weightVariation)*standardWeight
  }
}
extension CoinHandler: CoinHandlerProtocol {

  // 1
  public func handleCoinValidation(_ unknownCoin: Coin) ->
    Coin? {
    guard let coin = createCoin(from: unknownCoin) else {
      return next?.handleCoinValidation(unknownCoin)
    }
    return coin
  }
  // 2
  private func createCoin(from unknownCoin: Coin) -> Coin? {
    print("Attempt to create \(coinType)")
    guard diameterRange.contains(unknownCoin.diameter) else {
      print("Invalid diameter")
      return nil
    }
    guard weightRange.contains(unknownCoin.weight) else {
      print("Invalid weight")
      return nil
    }
    let coin = coinType.init(diameter: unknownCoin.diameter,
                             weight: unknownCoin.weight)
    print("Created \(coin)")
    return coin
  }
}
// MARK: - Client
// 1
public class VendingMachine {

  // 2
  public let coinHandler: CoinHandler
  public var coins: [Coin] = []

  // 3
  public init(coinHandler: CoinHandler) {
    self.coinHandler = coinHandler
  }
}
public func insertCoin(_ unknownCoin: Coin) {

  // 1
  guard let coin = coinHandler.handleCoinValidation(unknownCoin)
    else {
    print("Coin rejected: \(unknownCoin)")
    return
  }
  
  // 2
  print("Coin Accepted: \(coin)")
  coins.append(coin)

  // 3
  let dollarValue = coins.reduce(0, { $0 + $1.dollarValue })
  print("")
  print("Coins Total Value: $\(dollarValue)")

  // 4
  let weight = coins.reduce(0, { $0 + $1.weight })
  print("Coins Total Weight: \(weight) g")
  print("")
}
// MARK: - Example
// 1
let pennyHandler = CoinHandler(coinType: Penny.self)
let nickleHandler = CoinHandler(coinType: Nickel.self)
let dimeHandler = CoinHandler(coinType: Dime.self)
let quarterHandler = CoinHandler(coinType: Quarter.self)

// 2
pennyHandler.next = nickleHandler
nickleHandler.next = dimeHandler
dimeHandler.next = quarterHandler

// 3
let vendingMachine = VendingMachine(coinHandler: pennyHandler)
let penny = Penny()
vendingMachine.insertCoin(penny)
Attempt to create Penny
Created Penny {diameter: 0.750,
  dollarValue: $0.01, weight: 2.500}
Accepted Coin: Penny {diameter: 0.750,
  dollarValue: $0.01, weight: 2.500}
  
Coins Total Value: $0.01
Coins Total Weight: 2.5 g
let quarter = Coin(diameter: Quarter.standardDiameter,
                   weight: Quarter.standardWeight)
vendingMachine.insertCoin(quarter)
Attempt to create Penny
Invalid diameter
Attempt to create Nickel
Invalid diameter
Attempt to create Dime
Invalid diameter
Attempt to create Quarter
Created Quarter {diameter: 0.955,
  dollarValue: $0.25, weight: 5.670}
Accepted Coin: Quarter {diameter: 0.955,
  dollarValue: $0.25, weight: 5.670}
  
Coins Total Value: $0.26
Coins Total Weight: 8.17 g
let invalidDime = Coin(diameter: Quarter.standardDiameter,
                       weight: Dime.standardWeight)
vendingMachine.insertCoin(invalidDime)
Attempt to create Penny
Invalid diameter
Attempt to create Nickel
Invalid diameter
Attempt to create Dime
Invalid diameter
Attempt to create Quarter
Invalid weight
Coin rejected: Coin {diameter: 0.955,
  dollarValue: $0.00, weight: 2.268}

你应该小心吗?

责任链模式最适合可靠的处理程序,这些处理程序可以非常快地确定是否要处理事件。小心创建一个或多个慢速将事件传递给下一个处理程序的处理程序。

教程项目

您将构建一个名为的应用程序 rwsecret. 在这一章当中。此应用程序允许用户通过尝试用户提供的几个已知密码来解密密码。

Decryption failed!
secretMessage.decrypted = passwordClient.decrypt(secretMessage.encrypted)
import Foundation

public protocol DecryptionHandlerProtocol {
  var next: DecryptionHandlerProtocol? { get }
  func decrypt(data encryptedData: Data) -> String?
}
import RNCryptor

public class DecryptionHandler {

  // MARK: - Instance Properties
  public var next: DecryptionHandlerProtocol?
  public let password: String

  public init(password: String) {
    self.password = password
  }
}
extension DecryptionHandler: DecryptionHandlerProtocol {

  public func decrypt(data encryptedData: Data) -> String? {
    guard let data = try? RNCryptor.decrypt(
      data: encryptedData,
      withPassword: password),
      let text = String(data: data, encoding: .utf8) else {
        return next?.decrypt(data: encryptedData)
    }
    return text
  }
}
private var decryptionHandler: DecryptionHandlerProtocol?
// 1
guard passwords.count > 0 else {
  decryptionHandler = nil
  return
}

// 2
var current = DecryptionHandler(password: passwords.first!)
decryptionHandler = current

// 3
for i in 1 ..< passwords.count {
  let next = DecryptionHandler(password: passwords[i])
  current.next = next
  current = next
}
guard let data = Data(base64Encoded: base64EncodedString),
  let value = decryptionHandler?.decrypt(data: data) else {
    return nil
}
return value

关键点

您了解了本章中的责任链模式。以下是其关键点:

然后去哪儿?

使用责任链模式,您创建了一个秘密消息应用程序,该应用程序使用用户提供的密码解密消息。您可以添加到Rwsecret的许多功能:

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

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

© 2021 Razeware LLC

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

现在解锁

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