Паттерн Декоратор

Паттерн Декоратор относится к структурным паттернам проектирования.

Декоратор используется как альтернатива наследованию для расширения функционала классов.
Имеется задача расширения функционала приложения в зависимости от типа продукта. Заказчику необходимы три типа продукта – 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