設計模式二:策略模式(Strategy Pattern)

一、原理理解

在開發中,有許多算法可以實現某一功能,如查找、排序等,一種常用的方法是硬編碼在一個類中,如需要提供多種查找算法,可以將這些算法寫到一個類中,在該類中提供多個方法,每一個方法對應一個具體的查找算法。除此也可以將這些查找算法封裝在一個統一的方法中,通過if…else…等條件判斷語句來進行選擇。這兩種實現方法我們都可以稱之爲硬編碼,如果需要增加一種新的查找算法,則需要修改我們所封裝的那個類的源代碼。更換查找算法,也需要修改客戶端調用代碼。在這個自定義類中封裝了大量查找算法,該類代碼很複雜,維護起來很困難。

爲了解決這些問題,可以定義一些獨立的類來封裝不同的算法,每一個類封裝一個具體的算法。在這裏,每一個封裝算法的類我們都可以稱之爲策略(Strategy)。爲了保證這些策略的一致性,一般會用一個抽象的策略類來做算法的定義,而具體每種算法則對應一個具體策略類。

策略模式(Strategy Pattern):定義一系列算法,將每一個算法封裝起來,並讓它們可以相互替換。策略模式讓算法獨立於使用它的客戶而變化,也稱爲政策模式(Policy Pattern)。

策略模式包含如下角色:

  1. Context: 環境類
  2. Strategy: 抽象策略類
  3. ConcreteStrategy: 具體策略類

如圖所示:


策略模式是對算法的封裝,它把算法的責任和算法本身分割開,委派給不同的對象管理。策略模式,就是 “準備一組算法,並將每一個算法封裝起來,使得它們可以互換”。

在策略模式中,應當由客戶端自己決定在什麼情況下使用什麼具體策略角色。
策略模式僅僅封裝算法,提供將新算法插入到已有系統中,以及從系統中刪除老算法。策略模式並不決定在何時使用何種算法,算法的選擇由客戶端來決定。這一定程度上提高了系統的靈活性,但是客戶端需要理解所有具體策略類之間的區別,以便選擇在不同情景選擇最合適的算法,這也是策略模式的缺點之一,在一定程度上增加了客戶端的使用難度。

二、實踐示例

本例實現一個答題 demo。用戶點擊答題時候,可以選擇是隨機答題還是順序答題。
項目結構如下:


  1. 首先定義 QuestionStrategy 協議:
public protocol QuestionStrategy: class {

  var title: String { get }

  var correctCount: Int { get }
  var incorrectCount: Int { get }

  func advanceToNextQuestion() -> Bool

  func currentQuestion() -> Question

  func markQuestionCorrect(_ question: Question)
  func markQuestionIncorrect(_ question: Question)

  func questionIndexTitle() -> String
}
  1. 分別實現隨機答題、順序答題算法:

RandomQuestionStrategy:

import GameplayKit.GKRandomSource

public class RandomQuestionStrategy: QuestionStrategy {

  // MARK: - Properties
  public var correctCount: Int = 0
  public var incorrectCount: Int = 0
  private let questionGroup: QuestionGroup
  private var questionIndex = 0
  private let questions: [Question]

  // MARK: - Object Lifecycle
  public init(questionGroup: QuestionGroup) {
    self.questionGroup = questionGroup

    let randomSource = GKRandomSource.sharedRandom()
    self.questions =
      randomSource.arrayByShufflingObjects(
        in: questionGroup.questions) as! [Question]
  }

  // MARK: - QuestionStrategy
  public var title: String {
    return questionGroup.title
  }

  public func currentQuestion() -> Question {
    return questions[questionIndex]
  }

  public func advanceToNextQuestion() -> Bool {
    guard questionIndex + 1 < questions.count else {
      return false
    }
    questionIndex += 1
    return true
  }

  public func markQuestionCorrect(_ question: Question) {
    correctCount += 1
  }

  public func markQuestionIncorrect(_ question: Question) {
    incorrectCount += 1
  }

  public func questionIndexTitle() -> String {
    return "\(questionIndex + 1)/\(questions.count)"
  }
}

SequentialQuestionStrategy:

public class SequentialQuestionStrategy: QuestionStrategy {
  
  // MARK: - Properties
  public var correctCount: Int = 0
  public var incorrectCount: Int = 0
  private let questionGroup: QuestionGroup
  private var questionIndex = 0
  
  // MARK: - Object Lifecycle
  public init(questionGroup: QuestionGroup) {
    self.questionGroup = questionGroup
  }
  
  // MARK: - QuestionStrategy
  public var title: String {
    return questionGroup.title
  }
  
  public func currentQuestion() -> Question {
    return questionGroup.questions[questionIndex]
  }
  
  public func advanceToNextQuestion() -> Bool {
    guard questionIndex + 1 <
      questionGroup.questions.count else {
        return false
    }
    questionIndex += 1
    return true
  }
  
  public func markQuestionCorrect(_ question: Question) {
    correctCount += 1
  }
  
  public func markQuestionIncorrect(_ question: Question) {
    incorrectCount += 1
  }
  
  public func questionIndexTitle() -> String {
    return "\(questionIndex + 1)/" +
    "\(questionGroup.questions.count)"
  }
}

這兩個策略同時遵循 QuestionStrategy ,只是對算法的實現不同,一個是隨機生成題目,一個是順序生成題目。

  1. 使用方法:

當從 SelectQuestionGroupViewController 頁面 push 到QuestionViewController 時候,把自己選好的 QuestionStrategy (或者順序、或者隨機)賦值給 QuestionViewController。

  public override func prepare(for segue: UIStoryboardSegue,
                               sender: Any?) {
    guard let viewController = segue.destination
      as? QuestionViewController else { return }
    // 這裏根據需要選擇具體的實現策略
    viewController.questionStrategy = SequentialQuestionStrategy(
      questionGroup: selectedQuestionGroup)
    viewController.delegate = self
  }
  1. QuestionViewController 頁面調用相應的方法:
 @IBAction func handleCorrect(_ sender: Any) {
    let question = questionStrategy.currentQuestion()
    questionStrategy.markQuestionCorrect(question)

    questionView.correctCountLabel.text =
      String(questionStrategy.correctCount)
    showNextQuestion()
  }

  @IBAction func handleIncorrect(_ sender: Any) {
    let question = questionStrategy.currentQuestion()
    questionStrategy.markQuestionIncorrect(question)

    questionView.incorrectCountLabel.text =
      String(questionStrategy.incorrectCount)
    showNextQuestion()
  }
三、小結
  1. 什麼時候使用策略模式?

當有兩個或多個可選擇的不同行爲時,可使用策略模式。

策略模式類似於 delegation 模式:兩種模式都依賴於協議而不是具體對象來提高靈活性。 因此,任何實現策略協議的對象都可以在運行時用作策略。

與 delegation 不同,策略模式使用一系列對象。

delegation 經常在運行時就已經確定了, 例如,可以從Interface Builder 設置 UITableView 的 dataSource 和delegate,並且在運行時很少更改它們。

但是,策略旨在在運行時輕鬆互換。

  1. 注意:
  • 策略模式定義了一系列可在運行時設置或切換的可互換對象。
  • 此模式包含三個部分:使用策略的對象,策略協議和一系列策略對象。
  • 策略模式類似於委託模式:兩種模式都使用協議來實現靈活性。 然而,與委託模式不同,策略意味着在運行時切換,而委託通常是固定不變的。

demo 下載

參考:

  1. Design Patterns in Swift: Strategy Pattern
  2. how-does-the-strategy-pattern-work
  3. 策略模式 strategy pattern
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章