-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Builder #27
Open
xsdc
wants to merge
6
commits into
main
Choose a base branch
from
builder
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Builder #27
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,95 +10,187 @@ | |
|
||
## Pattern overview | ||
|
||
- The Builder pattern allows for the construction of a complex object step by step. | ||
- The pattern allows for the construction of different representations of the object using the same construction process. | ||
- For example, consider a car. The car can be built with different features, such as the color, the interior, the wheels, and so on. | ||
- The Builder pattern allows for the construction of a car with different features using the same construction process. | ||
- The Builder pattern is used to construct objects piece by piece. | ||
|
||
- Each part of the object is configured via a method on a builder object. | ||
|
||
- We are able to provide different representations of the object using the same construction process. | ||
|
||
## Problem statement | ||
|
||
- Before placing an order on the Apple Store, various information comes together to create an order | ||
- Products, product quantities, shipping information, and payment information. The order is created by combining all this information. | ||
- The Builder pattern can be used to create an order by combining all this information. | ||
- The Apple Store has a feature called the Apple Watch Studio, which allows customers to choose the size, material, and band for their Apple Watch. | ||
|
||
- There are different collections of Apple Watches, such as Series 10 and Hèrmes Series 10. | ||
|
||
- In the future, collections may be added or removed, and new sizes, materials, and bands may be introduced. | ||
|
||
- No matter which collection and options are chosen, the API where we submit configured Apple Watches remains the same. | ||
|
||
- We are faced with the challenges of constructing Apple Watches with various configurations and maintaining a consistent output. | ||
|
||
## Domain application | ||
- The Builder pattern gives us the needed flexibility in the construction process and allows us to conform to the requirements of the API. | ||
|
||
Builder: | ||
## Definitions | ||
|
||
Specifies an abstract interface for creating parts of a Product object. | ||
#### Product: | ||
|
||
The object that is being constructed. | ||
|
||
```swift | ||
protocol OrderBuilder { | ||
func setProduct(product: Product) | ||
func setQuantity(quantity: Int) | ||
func setShippingAddress(address: String) | ||
func setPaymentMethod(paymentMethod: String) | ||
func build() -> Order | ||
struct AppleWatch { | ||
var collection: String | ||
var size: String | ||
var material: String | ||
var band: String | ||
} | ||
``` | ||
|
||
ConcreteBuilder: | ||
#### Builder: | ||
|
||
- The protocol that declares the options for constructing the product. | ||
|
||
- Associated types are used here so that each concrete builder can define its own enum types for size, material, and band. | ||
|
||
- Constructs and assembles parts of the product by implementing the Builder interface. | ||
- Defines and keeps track of the representation it creates. | ||
- Provides an interface for retrieving the product. | ||
- Compare the `Series10Builder` and `HèrmesSeries10Builder` enum types to see this in action. | ||
|
||
- The protocol makes use of a fluent interface, allowing for the chaining of builder methods. | ||
|
||
```swift | ||
class CheckoutOrderBuilder: OrderBuilder { | ||
private var order = Order() | ||
protocol AppleWatchBuilder { | ||
associatedtype SizeType | ||
associatedtype MaterialType | ||
associatedtype BandType | ||
|
||
func setSize(_ size: SizeType) -> Self | ||
func setMaterial(_ material: MaterialType) -> Self | ||
func setBand(_ band: BandType) -> Self | ||
func build() -> AppleWatch | ||
} | ||
``` | ||
|
||
#### Concrete builders: | ||
|
||
- Conforms to the builder protocol and provides an implementation for building the product. | ||
|
||
- Two concrete builders are defined here: `Series10Builder` and `HèrmesSeries10Builder`. | ||
|
||
- Each one is tailored to a specific collection of Apple Watch. | ||
|
||
func setProduct(product: Product) { | ||
order.product = product | ||
- Type aliases are used here to pair the associated types for each concrete builder's enum type implementation. | ||
|
||
```swift | ||
class Series10Builder: AppleWatchBuilder { | ||
private var appleWatch = AppleWatch( | ||
collection: "Series 10", | ||
size: Size.fortyTwo.rawValue, | ||
material: Material.aluminum.rawValue, | ||
band: Band.sportBand.rawValue | ||
) | ||
|
||
enum Size: String { | ||
case fortyTwo = "42mm" | ||
case fortySix = "46mm" | ||
} | ||
|
||
func setQuantity(quantity: Int) { | ||
order.quantity = quantity | ||
enum Material: String { | ||
case aluminum = "Aluminum" | ||
case titanium = "Titanium" | ||
} | ||
|
||
func setShippingAddress(address: String) { | ||
order.shippingAddress = address | ||
enum Band: String { | ||
case sportBand = "Sport Band" | ||
case milaneseLoop = "Milanese Loop" | ||
} | ||
|
||
typealias SizeType = Size | ||
typealias MaterialType = Material | ||
typealias BandType = Band | ||
|
||
func setSize(_ size: Size) -> Self { | ||
appleWatch.size = size.rawValue | ||
return self | ||
} | ||
|
||
func setPaymentMethod(paymentMethod: String) { | ||
order.paymentMethod = paymentMethod | ||
func setMaterial(_ material: Material) -> Self { | ||
appleWatch.material = material.rawValue | ||
return self | ||
} | ||
|
||
func build() -> Order { | ||
return order | ||
func setBand(_ band: Band) -> Self { | ||
appleWatch.band = band.rawValue | ||
return self | ||
} | ||
|
||
func build() -> AppleWatch { | ||
return appleWatch | ||
} | ||
} | ||
``` | ||
|
||
Director: | ||
class HèrmesSeries10Builder: AppleWatchBuilder { | ||
private var appleWatch = AppleWatch( | ||
collection: "Hèrmes Series 10", | ||
size: Size.fortyTwo.rawValue, | ||
material: Material.titanium.rawValue, | ||
band: Band.torsade.rawValue | ||
) | ||
|
||
enum Size: String { | ||
case fortyTwo = "42mm" | ||
case fortySix = "46mm" | ||
} | ||
|
||
enum Material: String { | ||
case titanium = "Titanium" | ||
} | ||
|
||
Constructs an object using the Builder interface. | ||
enum Band: String { | ||
case torsade = "Torsade Single Tour" | ||
case grand = "Grand H" | ||
} | ||
|
||
```swift | ||
class OrderDirector { | ||
let builder: OrderBuilder | ||
typealias SizeType = Size | ||
typealias MaterialType = Material | ||
typealias BandType = Band | ||
|
||
func setSize(_ size: Size) -> Self { | ||
appleWatch.size = size.rawValue | ||
return self | ||
} | ||
|
||
func construct() -> Order { | ||
builder.setProduct(product: Product(name: "iPhone", price: 999.00)) | ||
builder.setQuantity(quantity: 1) | ||
builder.setShippingAddress(address: "123 7th Ave, New York, NY 10001") | ||
builder.setPaymentMethod(paymentMethod: "Credit Card") | ||
func setMaterial(_ material: Material) -> Self { | ||
appleWatch.material = material.rawValue | ||
return self | ||
} | ||
|
||
func setBand(_ band: Band) -> Self { | ||
appleWatch.band = band.rawValue | ||
return self | ||
} | ||
|
||
return builder.build() | ||
func build() -> AppleWatch { | ||
return appleWatch | ||
} | ||
} | ||
``` | ||
|
||
Product: | ||
|
||
- Represents the complex object under construction. | ||
- ConcreteBuilder builds the product's internal representation and defines the process by which it's assembled. | ||
- Includes classes that define the constituent parts, including interfaces for assembling the parts into the final result. | ||
## Example | ||
|
||
```swift | ||
struct Order { | ||
var product: Product? | ||
var quantity: Int? | ||
var shippingAddress: String? | ||
var paymentMethod: String? | ||
} | ||
let series10 = Series10Builder() | ||
|
||
print(series10.build()) // Default Series 10 Apple Watch | ||
// Output: AppleWatch(collection: "Series 10", size: "42mm", material: "Aluminum", band: "Sport Band") | ||
|
||
series10.setSize(.fortySix).setMaterial(.titanium).setBand(.milaneseLoop) // Update Series 10 Apple Watch | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really like how the builder pattern enables this chaining of methods. It's cool. Going to use this one in the future. |
||
print(series10.build()) | ||
// Output: AppleWatch(collection: "Series 10", size: "46mm", material: "Titanium", band: "Milanese Loop") | ||
|
||
let hermesSeries10 = HèrmesSeries10Builder() | ||
|
||
print(hermesSeries10.build()) // Default Hèrmes Series 10 Apple Watch | ||
// Output: AppleWatch(collection: "Hèrmes Series 10", size: "42mm", material: "Titanium", band: "Torsade Single Tour") | ||
|
||
hermesSeries10.setSize(.fortySix).setMaterial(.titanium).setBand(.grand) // Update Hèrmes Series 10 Apple Watch | ||
print(hermesSeries10.build()) | ||
// Output: AppleWatch(collection: "Hèrmes Series 10", size: "46mm", material: "Titanium", band: "Grand H") | ||
``` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice use of generics. I haven't tested this code. You 100% sure it works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works