Skip to content
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
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 147 additions & 55 deletions DesignPatterns/AppleStoreBuilder/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +60 to +62
Copy link
Collaborator

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.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works


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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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")
```