The Decorator pattern is a structural design pattern.
Decorator is used as an alternative to inheritance to extend the functionality of classes.
There is a task to expand the functionality of the application depending on the type of product. The customer needs three types of product – Basic, Professional, Ultimate.
Basic – counts the number of characters, Professional – Basic + capabilities prints text in capital letters, Ultimate – Basic + Professional + prints text with the inscription ULTIMATE.
We implement it using inheritance:
protocol Feature {
func textOperation(text: String)
}
class BasicVersionFeature: Feature {
func textOperation(text: String) {
print("\(text.count)")
}
}
class ProfessionalVersionFeature: BasicVersionFeature {
override func textOperation(text: String) {
super.textOperation(text: text)
print("\(text.uppercased())")
}
}
class UltimateVersionFeature: ProfessionalVersionFeature {
override func textOperation(text: String) {
super.textOperation(text: text)
print("ULTIMATE: \(text)")
}
}
let textToFormat = "Hello Decorator"
let basicProduct = BasicVersionFeature()
basicProduct.textOperation(text: textToFormat)
let professionalProduct = ProfessionalVersionFeature()
professionalProduct.textOperation(text: textToFormat)
let ultimateProduct = UltimateVersionFeature()
ultimateProduct.textOperation(text: textToFormat)
Now there is a requirement to implement the product “Ultimate Light” – Basic + Ultimate but without the capabilities of the Professional version. The first OUCH! happens, because you will have to create a separate class for such a simple task, duplicate the code.
Let’s continue the implementation using inheritance:
protocol Feature {
func textOperation(text: String)
}
class BasicVersionFeature: Feature {
func textOperation(text: String) {
print("\(text.count)")
}
}
class ProfessionalVersionFeature: BasicVersionFeature {
override func textOperation(text: String) {
super.textOperation(text: text)
print("\(text.uppercased())")
}
}
class UltimateVersionFeature: ProfessionalVersionFeature {
override func textOperation(text: String) {
super.textOperation(text: text)
print("ULTIMATE: \(text)")
}
}
class UltimateLightVersionFeature: BasicVersionFeature {
override func textOperation(text: String) {
super.textOperation(text: text)
print("ULTIMATE: \(text)")
}
}
let textToFormat = "Hello Decorator"
let basicProduct = BasicVersionFeature()
basicProduct.textOperation(text: textToFormat)
let professionalProduct = ProfessionalVersionFeature()
professionalProduct.textOperation(text: textToFormat)
let ultimateProduct = UltimateVersionFeature()
ultimateProduct.textOperation(text: textToFormat)
let ultimateLightProduct = UltimateLightVersionFeature()
ultimateLightProduct.textOperation(text: textToFormat)
The example can be developed further for clarity, but even now the complexity of supporting a system based on inheritance is visible – it is cumbersome and lacks flexibility.
A decorator is a set of protocols describing functionality, an abstract class containing a reference to a child concrete instance of the decorator class that extends the functionality.
Let’s rewrite the example above using the pattern:
protocol Feature {
func textOperation(text: String)
}
class FeatureDecorator: Feature {
private var feature: Feature?
init(feature: Feature? = nil) {
self.feature = feature
}
func textOperation(text: String) {
feature?.textOperation(text: text)
}
}
class BasicVersionFeature: FeatureDecorator {
override func textOperation(text: String) {
super.textOperation(text: text)
print("\(text.count)")
}
}
class ProfessionalVersionFeature: FeatureDecorator {
override func textOperation(text: String) {
super.textOperation(text: text)
print("\(text.uppercased())")
}
}
class UltimateVersionFeature: FeatureDecorator {
override func textOperation(text: String) {
super.textOperation(text: text)
print("ULTIMATE: \(text)")
}
}
let textToFormat = "Hello Decorator"
let basicProduct = BasicVersionFeature(feature: UltimateVersionFeature())
basicProduct.textOperation(text: textToFormat)
let professionalProduct = ProfessionalVersionFeature(feature: UltimateVersionFeature())
professionalProduct.textOperation(text: textToFormat)
let ultimateProduct = BasicVersionFeature(feature: UltimateVersionFeature(feature: ProfessionalVersionFeature()))
ultimateProduct.textOperation(text: textToFormat)
let ultimateLightProduct = BasicVersionFeature(feature: UltimateVersionFeature())
ultimateLightProduct.textOperation(text: textToFormat)
Now we can create variations of any type of product – it is enough to initialize the combined types at the stage of application launch, the example below is the creation of the Ultimate + Professional version:
ultimateProfessionalProduct.textOperation(text: textToFormat)
Sources
https://refactoring.guru/ru/design-patterns/decorator
Source code
https://gitlab.com/demensdeum/patterns