- director 接受参数并协调 builder。通常是一个 view controller 或者 view controller 中的一个 helper 类。
- product 是创建的复杂对象。 这可以是一个结构体或类,具体取决于所需的引用语义。 它通常是一个模型,但它可以为任何类型。
- 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
}
}
效果图: