-
Notifications
You must be signed in to change notification settings - Fork 3
Builder
Jakub Vano edited this page Jun 27, 2017
·
4 revisions
Builder pattern is commonly used when creating/modifying domain model via form. Typically flow looks like this:
-
View
passes user-entered raw values (String
s) toPresenter
-
Presenter
converts raw data to partial models (i.e.String -> Double
) viaParser
s,Presenter
also handles empty strings. -
Presenter
passes partial models toBuilder
-
View
invokes some sort ofsubmit
action onPresenter
-
Presenter
callsBuilder.build()
-
Builder
constructs final domain model, while performing necessary validations, and returns either model or validation error / errors. -
Presenter
handles the results.
Models and interfaces:
enum PaymentOrderError {
case invalidAmount
case missingAmount
case invalidRecipient
case missingRecipient
}
struct PaymentOrder {
let amount: Money
let recipient: Iban
let note: String?
}
protocol PaymentOrderBuilder {
var amount: Parsed<Double> { get set }
var recipient: String? { get set }
var note: String? { get set }
func build() -> Result<PaymentOrder, CompositeError<PaymentOrderError>>
}
protocol PaymentOrderPresenter {
func updateAmount(_ amount: String)
func updateRecipient(_ recipient: String)
func updateNote(_ note: String)
func submit()
}
Implementations:
final class PaymentOrderPresenterImpl: PaymentOrderPresenter {
private let builder: PaymentOrderBuilder
private let doubleParser: StringToDoubleParser
init(builder: PaymentOrderBuilder, doubleParser: StringToDoubleParser) {
self.builder = builder
self.doubleParser = doubleParser
}
func updateAmount(_ amount: String) {
builder.amount = doubleParser.parse(amount)
}
func updateRecipient(_ recipient: String) {
builder.recipient = recipient.nilIfEmpty
}
func updateNote(_ note: String) {
builder.note = note.nilIfEmpty
}
func submit() {
switch builder.build() {
case .success(let paymentOrder):
handlePaymentOrder(paymentOrder)
case .failure(let error):
handleError(error)
}
}
private func handlePaymentOrder(_ paymentOrder: PaymentOrder) {
}
private func handleError(_ error: CompositeError<PaymentOrderError>) {
}
}
final class PaymentOrderBuilderImpl: PaymentOrderBuilder {
var amount: Parsed<Double> = .missing
var recipient: String?
var note: String?
private let ibanValidator: IbanValidator
init(ibanValidator: IbanValidator) {
self.ibanValidator = ibanValidator
}
func build() -> Result<PaymentOrder, CompositeError<PaymentOrderError>>{
return buildAmount().mapError(CompositeError.init)
.pack(buildRecipient().mapError(CompositeError.init))
.pack(Result.success(note))
.map(flatten)
.map(PaymentOrder.init)
}
private func buildAmount() -> Result<Money, PaymentOrderError> {
switch amount {
case .missing:
return .failure(.missingAmount)
case .invalid:
return .failure(.invalidAmount)
case .value(let amount):
return .success(Money(amount: amount, currency: .EUR))
}
}
private func buildRecipient() -> Result<Iban, PaymentOrderError> {
guard let recipient = recipient else {
return .failure(.missingRecipient)
}
guard ibanValidator.isValid(recipient) else {
return .failure(.invalidRecipient)
}
return .success(Iban(recipient))
}
}