Паттерн Декоратор относится к структурным паттернам проектирования.
Декоратор используется как альтернатива наследованию для расширения функционала классов.
Имеется задача расширения функционала приложения в зависимости от типа продукта. Заказчику необходимы три типа продукта – Basic, Professional, Ultimate.
Basic – считает количество символов, Professional – возможности Basic + печатает текст большими буквами, Ultimate – Basic + Professional + печатает текст с надписью ULTIMATE.
Реализуем с помощью наследования:
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)
Теперь появляется требование реализовать продукт “Ultimate Light” – Basic + Ultimate но без возможностей Professional версии. Случается первый ОЙ!, т.к. придется создавать отдельный класс для такой простой задачи, дублировать код.
Продолжим реализацию с помощью наследования:
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)
Пример можно развивать для наглядности и дальше, но уже и сейчас видна сложность поддержки системы основанной на базе наследования – громоздкость и отсутствие гибкости.
Декоратор представляет из себя совокупность протокола описывающего функционал, абстрактного класса содержащего референс на дочерний конкретный экземпляр класса декоратора который расширяет функционал.
Перепишем пример выше с использованием паттерна:
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)
Теперь мы можем создавать вариации продукта любого типа – достаточно инициализировать комбинированные типы на этапе запуска приложения, пример ниже представляет из себя создание Ultimate + Professional версии:
let ultimateProfessionalProduct = UltimateVersionFeature(feature: ProfessionalVersionFeature())
ultimateProfessionalProduct.textOperation(text: textToFormat)
Источники
https://refactoring.guru/ru/design-patterns/decorator
Исходный код
https://gitlab.com/demensdeum/patterns