设计模式二:策略模式(Strategy Pattern)

一、原理理解

在开发中,有许多算法可以实现某一功能,如查找、排序等,一种常用的方法是硬编码在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法。除此也可以将这些查找算法封装在一个统一的方法中,通过if…else…等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,则需要修改我们所封装的那个类的源代码。更换查找算法,也需要修改客户端调用代码。在这个自定义类中封装了大量查找算法,该类代码很复杂,维护起来很困难。

为了解决这些问题,可以定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法。在这里,每一个封装算法的类我们都可以称之为策略(Strategy)。为了保证这些策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体每种算法则对应一个具体策略类。

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy Pattern)。

策略模式包含如下角色:

  1. Context: 环境类
  2. Strategy: 抽象策略类
  3. ConcreteStrategy: 具体策略类

如图所示:


130752-2dff13a85567b252.jpg
Strategy

策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式,就是 “准备一组算法,并将每一个算法封装起来,使得它们可以互换”。

在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。
策略模式仅仅封装算法,提供将新算法插入到已有系统中,以及从系统中删除老算法。策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择在不同情景选择最合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。

二、实践示例

本例实现一个答题 demo。用户点击答题时候,可以选择是随机答题还是顺序答题。
项目结构如下:


130752-e189f758d07196ec.png
  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()
  }
三、小结
130752-fd562b63c47a33c6.png
strategy pattern
  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

猜你喜欢

转载自blog.csdn.net/weixin_34149796/article/details/87082526