???? Weekly Snippets
Every Sunday I write an email newsletter with some thoughts, life lessons and links to articles / books I enjoyed that week. I’d love for you to join.
Design Pattern - Structural: Decorator in Swift
Hey friends, as I promised on Instagram I am sharing with you my notes about coding and design pattern.
???? Definition
Decorator is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.
???????? Problem
Imagine if we are to create RPG game in our model layer we could have protocol defining races in game.
protocol Race {
func getHealth() -> Int
}
Nothing too fancy. We can use this protocol to create different entities in our game like:
struct Dwarf: Race {
func getHealth() -> Int {
return 1
}
}
That is no problem right? Well not currently, but as it is a RPG game it can easier run out of control.
struct DwarfGeneric: Character {
func getHealth() -> Int {
return 10
}
}
struct DwarfBarbarian: Character {
func getHealth() -> Int {
return 15
}
}
struct DwarfWarlord: Character {
func getHealth() -> Int {
return 20
}
}
Here you have only few Dwarf structs. Every one of them is separate value which you have to create explicitly. If your game would only contain those Dwarf there is no hustle. Above code is ok. But if you consider adding Elves, Humans, Orcs and adding more classed, maybe add also getMana() function you would be better with the decorator pattern in your toolbox.
The Decorator
The decorator has 4 main actors. Here they are:
- Core Component — it is the base class or protocol which our base object will subclass / implement. In given example it is Character protocol.
- Concrete Component — implemetation (or again, subclass) of Core Component.
- Decorator — again implementation (or subclass) of Core Component. Concrete Decorator has capability of wrapping around Components or other Decorators and building structures. Subrace protocol is our Decorator here.
- Concrete Decorator — implementation of Decorator. All structs like Warlord, Mage or Epic are Concrete Decorators.
/*
* Core Component:
* Every base race have to be hable to "say" how much health she has.
*/
protocol Race {
func getHealth() -> Int
}
/*
* Concrete Component:
* Now that we have a protocol that every base race
* have to fit, we can create the concrete component
* => the dwarf base race.
*/
struct Dwarf : Race {
func getHealth() -> Int {
return 10
}
}
/*
* Decorator:
* But in every beautiful fantasy world there are a
* lot of subraces like mountain-dwarf, golden-dwarf,
* fire-dwarf...So we have to create a new protocol that
* rapresent our Decorator. In other world our subrace
* "abstract" entity. She wrap the behavior of the race
* protocol.
*/
protocol Subrace: Race {
var base: Race { get }
}
/*
* Concrete Decorator:
* Now we have to implement our subraces, for instance
* the Golden-Dwarf. To do it we have to create a struct
* that fit concretely the Decorator since the golden-dwarf
* is a subrace - not a base race. So our struct will have
* a base race property and a proper get health function that
* combine the behavior of the base race get health with something
* else.
*/
struct GoldenDwarf: Subrace {
var base: Race
func getHealth() -> Int {
return base.getHealth() + 10
}
}
// So in our world we will have Dwarf
var dwarf = Dwarf()
// With 10 health
print(dwarf.getHealth()) // 10
// But we will also have beautiful golden dwarf.
var goldenDwarf = GoldenDwarf(base: dwarf)
// With a strong body
print(goldenDwarf.getHealth()) // 20
The Decorator comes handy in following cases:
- When you have to add new functionality or extend existing in dynamic and transparent way.
- If you need to reverse changes to objects.
- If subclassing is not so practical due to big number of subclasses being created in process (that is exactly the case in our code sample).
The main benefit of the pattern is flexibility advantage over just subclassing. In Swift we have different ways like protocols to avoid inheritance but it was the big deal back then. Still when using my struct/protocol implementation it is nice, structured and memory efficient way to organize certain parts of your application. It is also ok to add responsibilities to objects like for example Warlord Subrace
could have Warlord specific function battleCry()
. It makes sense.
struct Elf: Race {
func getHealth() -> Int {
return 100
}
}
struct WarlordElf: Subrace {
var base: Race
func getHealth() -> Int {
return base.getHealth() + 50;
}
func battleCry() -> String{
return "For Honor and Glory!"
}
}
// Elf()
var elf = Elf()
print(elf.getHealth()) // 100
// WarlordElf()
var warlordElf = WarlordElf()
print(warlordElf.getHealth()) // 150
// "For Honor And Glory!"
print(warlordElf.battleCry())
The Decorator also helps you avoid creating large objects (or values, damn you Swift) with many functions at base level of hierarchy and reduce code repetition. Imagine how much boilerplate it would require to create all classes for all races!
What you have to keep in mind is that objects in Decorator pattern are not the same. They could be but it is safe to be cautious and assume they could be different. Also be prepared for many small, similar files in one way it helps but your project can be easily cluttered by them.
???? Weekly Snippets
Every Sunday I write an email newsletter with some thoughts, life lessons and links to articles / books I enjoyed that week. I’d love for you to join.