Зачем нужен?
Паттерн Decorator применяется для динамического добавления поведения или состояния объектам. Наследования не является допустимой альтернативой, т.к оно статично(этап компиляции) и применяется ко всему классу.
Соответствует open/closed принципу SOLID: класс открыт для расширения, но закрыт для изменения
Определение
- динамически наделяет объект новыми возможностями и является гибкой альтернативой наследованию в области расширения функциональности
- декорирование базового объекта путем рекурсивного завертывания(wrapping) дополнений, определяемых пользователем
Реализация
- Определите общий интерфейс(таким образом классы будут взаимозаменяемы)
- Создайте класс основного компонента(наследует общий интерфейс)
- Создайте базовый класс декоратора(наследует общий интерфейс)
- У декоратора с помощью композиции объявите переменную типа общего интерфейса
- Инициализируйте этот объект в инициализаторе декоратора
- У декоратора делегируйте методы общего интерфейса этому объекту
- Создайте конкретные классы декоратора для каждого дополнения
- Конкретные классы декоратора делегируют поведение базовому классу декоратора + добавляют свое
- Клиент конфигурирует тип и порядок базового и дополнительных компонентов
Пример
Допустим мы владеем кафе и у нас в продаже имеются различные виды кофе:
- кофе темной обжарки
- кофе с шоколадом
- кофе со взбитыми сливками
Можно реализовать базовый класс кофе и наследовать различные варианты и комбинации конкретный типов кофе, т.о получим такой набор классов:
- базовый класс кофе
- кофе темной обжарки
- кофе темной обжарки с шоколадом
- кофе темной обжарки со взбитыми сливками
- кофе темной обжарки с шоколадом и взбитыми сливками
- ……….
Другой способ решения - применение декоратора: инкапсулируем исходный объект в интерфейсе, наследуем полученный интерфейс и основным объектом, и декоратором. При этом добавляем с помощью композиции переменную с типом интерфейса декоратору и переопределяем методы на использование делегирования. Создаем конкретные дополнения, делегируем базовому декоратору и добавляем конкретное поведение.
Определяем общий интерфейс
// абстрактный компонент, общий интерфейс
protocol Beverage {
func cost() -> Double
func description() -> String
}
Создаем классы основных компонентов
// конкретный компонент
class Espresso: Beverage {
func description() -> String {
return "Espresso"
}
func cost() -> Double {
return 1.99
}
}
// конкретный компонент
class DarkRoast: Beverage {
func description() -> String {
return "Dark Roast"
}
func cost() -> Double {
return 2.99
}
}
Создаем базовый класс декоратора, с помощью композиции объявляем переменную типа общего интерфейса, инициализируем переменную в инициализаторе и делегируем методы общего интерфейса(пункты 3, 4, 5, 6)
// абстрактный декоратор
class CondimentDecorator: Beverage {
private var decoratedBeverage: Beverage
init(beverage: Beverage) {
decoratedBeverage = beverage
}
func description() -> String {
return decoratedBeverage.description()
}
func cost() -> Double {
return decoratedBeverage.cost()
}
}
Создаем конкретные классы декоратора, делегируем поведение базовому декоратору и добавляем свое(пункты 7, 8)
// конкретное дополнение
class Mocha: CondimentDecorator {
override func description() -> String {
return super.description() + ", Mocha"
}
override func cost() -> Double {
return super.cost() + 0.5
}
}
// конкретное дополнение
class Whip: CondimentDecorator {
override func description() -> String {
return super.description() + ", Whip"
}
override func cost() -> Double {
return super.cost() + 0.2
}
}
Конфигурируем нужный объект, комбинируя основной компонент и дополнения
var myEspresso:Beverage = Espresso()
// Espresso 1.99
var myDarkRoast:Beverage = DarkRoast()
// Dark Roast 2.99
myDarkRoast = Mocha(beverage: myDarkRoast)
myDarkRoast = Mocha(beverage: myDarkRoast)
myDarkRoast = Whip(beverage: myDarkRoast)
// Dark Roast, Mocha, Mocha, Whip 4.19
В качестве еще одного примера:
- базовый объект - новогодняя елка
- класс декоратора - украшения
- конкретные классы декоратора - игрушки, гирлянда и тд