设计模式六:Builder Pattern

builder
  1. director 接受参数并协调 builder。通常是一个 view controller 或者 view controller 中的一个 helper 类。
  2. product 是创建的复杂对象。 这可以是一个结构体或类,具体取决于所需的引用语义。 它通常是一个模型,但它可以为任何类型。
  3. builder 接受逐步输入并处理 product 的创建。 它通常是一个类,因此可以通过引用重用它。
什么时候使用 Builder Pattern ?

如果要使用一系列步骤创建复杂对象,请使用 Builder 模式。

当 product 需要多个输入时,此模式特别有效。 Builder 将这些输入用于创建 product 的方式进行抽象,并按照 director 想要提供的顺序接受它们。

例如,您可以使用此模式来实施 “hamburger builder” 。该 product 可以是汉堡包模型,其中包含肉类选择,浇头和调味汁等输入。 director 可以是一个员工对象,它知道如何构建汉堡包,或者它可以是一个接受用户输入的视图控制器。

因此,“hamburger builder” 可以按任何顺序接受肉类选择,浇头和酱料,并根据要求制作汉堡包。

What should you be careful about?

Builder Pattern 适合那些需要多个输入,使用一系列步骤创建的复杂 product 。 如果您的 product 没有多个输入或无法一步一步创建,那么 Builder Pattern 不适合使用。

一个示例:

CreateQuestionGroupViewController 使用 table view 接受用于创建 QuestionGroup 的输入,它显示 CreateQuestionGroupTitleCell 和CreateQuestionCell 以收集用户的输入。因此,CreateQuestionGroupViewController 是 director,而 QuestionGroup 是 product。
QuestionBuilder 和 QuestionGroupBuilder 为 builder。

CreateQuestionGroupViewController.swift:

public protocol CreateQuestionGroupViewControllerDelegate {
  func createQuestionGroupViewControllerDidCancel(_ viewController: CreateQuestionGroupViewController)

  func createQuestionGroupViewController(_ viewController: CreateQuestionGroupViewController,
                                         created questionGroup: QuestionGroup)
}

public class CreateQuestionGroupViewController: UITableViewController {

  // MARK: - Properties
  public var delegate: CreateQuestionGroupViewControllerDelegate?
  public let questionGroupBuilder = QuestionGroupBuilder()

  public override func viewDidLoad() {
    super.viewDidLoad()
    tableView.tableFooterView = UIView()
  }

  // MARK: - IBActions
  @IBAction func cancelPressed(_ sender: Any) {
    delegate?.createQuestionGroupViewControllerDidCancel(self)
  }

  @IBAction func savePressed(_ sender: Any) {
    do {
      let questionGroup = try questionGroupBuilder.build()
      delegate?.createQuestionGroupViewController(
        self, created: questionGroup)

    } catch {
      displayMissingInputsAlert()
    }
  }

  public func displayMissingInputsAlert() {
    let alert = UIAlertController(
      title: "Missing Inputs",
      message: "Please provide all non-optional values",
      preferredStyle: .alert)

    let okAction = UIAlertAction(title: "Ok",
                                 style: .default,
                                 handler: nil)
    alert.addAction(okAction)
    present(alert, animated: true, completion: nil)
  }
}

// MARK: - UITableViewDataSource
extension CreateQuestionGroupViewController {

  fileprivate struct CellIdentifiers {
    fileprivate static let add = "AddQuestionCell"
    fileprivate static let title = "CreateQuestionGroupTitleCell"
    fileprivate static let question = "CreateQuestionCell"
  }

  public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return questionGroupBuilder.questions.count + 2
  }

  public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let row = indexPath.row
    if row == 0 {
      return titleCell(from: tableView, for: indexPath)
    } else if row >= 1 &&
      row <= questionGroupBuilder.questions.count {
      return self.questionCell(from: tableView, for: indexPath)
    } else {
      return addQuestionGroupCell(from: tableView, for: indexPath)
    }
  }

  private func titleCell(from tableView: UITableView,
                         for indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.title,
                                             for: indexPath) as! CreateQuestionGroupTitleCell
    cell.delegate = self
    cell.titleTextField.text = questionGroupBuilder.title
    return cell
  }

  private func  questionCell(from tableView: UITableView,
                             for indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.question,
                                             for: indexPath) as! CreateQuestionCell
    cell.delegate = self
    let questionBuilder = self.questionBuilder(for: indexPath)
    cell.delegate = self
    cell.answerTextField.text = questionBuilder.answer
    cell.hintTextField.text = questionBuilder.hint
    cell.indexLabel.text = "Question \(indexPath.row)"
    cell.promptTextField.text = questionBuilder.prompt
    return cell
  }

  private func questionBuilder(for indexPath: IndexPath) -> QuestionBuilder {
    return questionGroupBuilder.questions[indexPath.row - 1]
  }

  private func addQuestionGroupCell(from tableView: UITableView,
                                    for indexPath: IndexPath) -> UITableViewCell {
    return tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.add,
                                         for: indexPath)
  }
}

// MARK: - UITableViewDelegate
extension CreateQuestionGroupViewController {

  public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    tableView.deselectRow(at: indexPath, animated: true)
    guard isLastIndexPath(indexPath) else { return }
    questionGroupBuilder.addNewQuestion()
    tableView.insertRows(at: [indexPath], with: .top)
  }

  private func isLastIndexPath(_ indexPath: IndexPath) -> Bool {
    return indexPath.row ==
      tableView.numberOfRows(inSection: indexPath.section) - 1
  }
}

// MARK: - CreateQuestionCellDelegate
extension CreateQuestionGroupViewController: CreateQuestionCellDelegate {

  public func createQuestionCell(_ cell: CreateQuestionCell,
                                 answerTextDidChange text: String) {
    questionBuilder(for: cell).answer = text
  }

  private func questionBuilder(for cell: CreateQuestionCell) -> QuestionBuilder  {
    let indexPath = tableView.indexPath(for: cell)!
    return questionBuilder(for: indexPath)
  }

  public func createQuestionCell(_ cell: CreateQuestionCell,
                                 hintTextDidChange text: String) {
    questionBuilder(for: cell).hint = text
  }

  public func createQuestionCell(_ cell: CreateQuestionCell,
                                 promptTextDidChange text: String) {
    questionBuilder(for: cell).prompt = text
  }
}

// MARK: - CreateQuestionGroupTitleCellDelegate
extension CreateQuestionGroupViewController: CreateQuestionGroupTitleCellDelegate {

  public func createQuestionGroupTitleCell(_ cell: CreateQuestionGroupTitleCell,
                                           titleTextDidChange text: String) {
    questionGroupBuilder.title = text
  }
}

QuestionGroupBuilder.swift:

public class QuestionGroupBuilder {

  public var questions = [QuestionBuilder()]
  public var title = ""

  public func addNewQuestion() {
    let question = QuestionBuilder()
    questions.append(question)
  }

  public func removeQuestion(at index: Int) {
    questions.remove(at: index)
  }

  public func build() throws -> QuestionGroup {
    guard self.title.count > 0 else { throw Error.missingTitle }
    guard self.questions.count > 0 else { throw Error.missingQuestions }
    let questions = try self.questions.map { try $0.build() }
    return QuestionGroup(questions: questions, title: title)
  }

  public enum Error: String, Swift.Error {
    case missingTitle
    case missingQuestions
  }
}

public class QuestionBuilder {
  public var answer = ""
  public var hint = ""
  public var prompt = ""

  public func build() throws -> Question {
    guard answer.count > 0 else { throw Error.missingAnswer }
    guard prompt.count > 0 else { throw Error.missingPrompt }
    return Question(answer: answer, hint: hint, prompt: prompt)
  }

  public enum Error: String, Swift.Error {
    case missingAnswer
    case missingPrompt
  }
}

效果图:


builder

Demo 下载

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容