Creational software design pattern deals with object creation mechanisms
Should be applied when:
- We have a superclass with multiple subclasses and we need to return one of the subclass based on the parameter
- We want to separates the code of instantiation a class to the Factory class (Loose Coupling)
How it solves the problem:
- Provides a static method with return-type of superclass or protocol and return a specific object based on the parameter
enumCarType{case lamborghini
case ferrari
}protocolCar{func run()}classLamborghini:Car{func run(){print("\(Lamborghini.self)")}}classFerrari:Car{func run(){print("\(Ferrari.self)")}}classCarFactory{
// return object based on the param
staticfunc create(carType:CarType)->Car{switch carType {case.lamborghini:
// initialization code here
returnLamborghini()case.ferrari:
// initialization code here
returnFerrari()}}}
// Usage
letlamborghini=CarFactory.create(carType:.lamborghini)letferrari=CarFactory.create(carType:.ferrari)Should be applied when:
- We want to create one object and globally use it the whole time
- We don’t want to create the same object again and again
- Do not overuse this pattern 🙂
How it solves the problem:
- A class contains one global shared instance property
- Use private constructor to ensure that the object can only be instantiated once
classDatabase{staticletinstance=Database()privateinit(){
// to prevent 2nd initialization
}func write(){print("Writing datatase")}}
// Usage
Database.instance.write()Should be applied when:
- We want to compose complex objects
- Object contains a lot of properties
- A constructor has too many parameters, it gets difficult to read and manage
How it solves the problem:
- Use an inner-class Builder to create the object part-by-part and provide a method that will return the final object
extensionUILabel{classBuilder{
// Component we want to build part by part
privateletlabel=UILabel(frame:.zero)func withBackgroundColor(_ color:UIColor)->Self{
label.backgroundColor = color
returnself}func withTextColor(_ color:UIColor)->Self{
label.textColor = color
returnself}func withFont(_ font:UIFont)->Self{
label.font = font
returnself}func withFrame(_ frame:CGRect)->Self{
label.frame = frame
returnself}func setText(_ text:String)->Self{
label.text = text
returnself}
// Return the final object
func build()->UILabel{return label
}}}
// Usage
letlabel=UILabel.Builder().withFont(.boldSystemFont(ofSize:18)).withBackgroundColor(.green).withTextColor(.white).withFrame(.zero).setText("Builder Pattern").build()Structural software design pattern deals with class structure mechanisms
Should be applied when:
- We want to attach additional behavior to an object at runtime without affecting the original object
- We don’t want to modify the object behavior using Inheritance
- Inheritance sometimes leads to complex and complicated structure
- Composition over Inheritance :)
How it solves the problem:
- Wrapping an object with Decorator class
protocolComputer{varcost:Double{get}vardescription:String{get}}
// Class we want to decorate
classDesktopComputer:Computer{varcost:Double{return300}vardescription:String{return"Desktop Computer"}}
// All decorators will inherite this class
classDesktopComputerDecorator:Computer{
// Object to be decorated
letcomputer:Computervarcost:Double{return computer.cost
}vardescription:String{return computer.description
}init(computer:Computer){self.computer = computer
}}
// Decorators
finalclassProcessorUpgrade:DesktopComputerDecorator{overridevarcost:Double{return computer.cost +100}overridevardescription:String{return computer.description +", core i7"}overrideinit(computer:Computer){
super.init(computer: computer)}}finalclassGraphicCardUpgrade:DesktopComputerDecorator{overridevarcost:Double{return computer.cost +50}overridevardescription:String{return computer.description +", NVIDIA GTX 1080"}overrideinit(computer:Computer){
super.init(computer: computer)}}
// Usage
vardesktop:Computer=DesktopComputer()
// decorate
desktop =ProcessorUpgrade(computer: desktop)
desktop =GraphicCardUpgrade(computer: desktop)
// or
//desktop = ProcessorUpgrade(computer: GraphicCardUpgrade(computer: desktop))
print(desktop.description +", $\(desktop.cost)")Should be applied when:
- Application uses large number of similar objects
- We want to minimize memory usage and increase performance
How it solves the problem:
- Keep a list of caching objects for future use, instead of creating new one every time
finalclassCar{varname:Stringinit(name:String){self.name = name
}}
// Create and cache Cars for future use
finalclassFlyweightCar{
// Flyweight objects
varcars:[String:Car]=[:]
// If brand name already exists, return cache
// Otherwise create and return new one
func getCarByBrand(name:String)->Car{iflet cacheCar =cars[name]{return cacheCar
}else{letcar=Car(name: name)cars[name]= car
return car
}}}
// Usage
letcar=FlyweightCar()
// create new BMW car
letbmw1= car.getCarByBrand(name:"BMW")
// get BMW car from the cache and reuse instead of creating new
letbmw2= car.getCarByBrand(name:"BMW")