Одним из наиболее важных принципов разработки программного обеспечения является принцип открытого-закрытого проектирования. Этот принцип проектирования подчеркивает, что классы должны быть открыты для расширения, но закрыты для модификации. Шаблон проектирования декоратора воплощает принцип открытого-закрытого дизайна.
С помощью шаблона проектирования декоратора вы можете легко расширить класс, придав ему новое поведение, не изменяя его существующий код. Шаблон декоратора делает это динамически во время выполнения, используя композицию. Этот шаблон проектирования известен как гибкая альтернатива использованию наследования для расширения поведения.
Как работает шаблон проектирования Decorator?
Хотя шаблон декоратора является альтернативой наследование классов, он включает в свой дизайн некоторые аспекты наследования. Ключевым аспектом шаблона декоратора является то, что все его классы связаны прямо или косвенно.
Типичный шаблон проектирования декоратора имеет следующую структуру:
Из приведенной выше диаграммы классов видно, что шаблон декоратора имеет четыре основных класса.
Компонент: это абстрактный класс (или интерфейс), который служит супертипом для шаблона декоратора.
БетонКомпонент: это объекты, которые вы можете украшать различными поведениями во время выполнения. Они наследуются от интерфейса компонента и реализуют его абстрактные функции.
Декоратор: этот класс является абстрактным и имеет тот же супертип, что и объект, который он украшает. На диаграмме классов вы увидите две связи между классами компонента и декоратора. Первые отношения относятся к наследованию; каждый декоратор это компонент. Второе отношение — это отношение композиции; каждый декоратор имеет (или обертывает) компонент.
БетонДекоратор: это отдельные декораторы, которые придают компоненту определенное поведение. Следует отметить, что каждый конкретный декоратор имеет переменную экземпляра, которая содержит ссылку на компонент.
Реализация шаблона проектирования Decorator в Java
Пример приложения для заказа пиццы может адекватно продемонстрировать, как использовать шаблон декоратора для разработки приложений. Этот образец приложения для пиццы позволяет клиентам заказывать пиццу с несколькими начинками. Первый класс шаблона декоратора — это интерфейс пиццы:
публичныйинтерфейсПицца{
публичныйабстрактный Нить описание();
публичныйабстрактныйдвойнойрасходы();
}
Интерфейс Pizza — это класс компонента. Таким образом, вы можете создать из него один или несколько конкретных классов. Компания по производству пиццы делает два основных типа пиццы на основе своего теста. Один вид пиццы имеет дрожжевое тесто:
публичныйсортДрожжиКоркаПиццареализуетПицца{
@Override
публичный Нить описание(){
возвращаться"Тесто для пиццы на дрожжах";
}
@Override
публичныйдвойнойрасходы(){
возвращаться18.00;
}
}
YeastCrustPizza — первая бетонная Java-класс интерфейса пиццы. Другой доступный тип пиццы - лепешка:
публичныйсортЛепешкаКоркаПиццареализуетПицца{
@Override
публичный Нить описание(){
возвращаться"Тесто для пиццы из лепешки";
}
@Override
публичныйдвойнойрасходы(){
возвращаться15.00;
}
}
Класс FlatbreadCrustPizza является вторым конкретным компонентом и, как и класс YeastCrustPizza, реализует все абстрактные функции интерфейса Pizza.
Декораторы
Класс декоратора всегда абстрактен, поэтому вы не можете создать новый экземпляр непосредственно из него. Но необходимо установить взаимосвязь между разными декораторами и компонентами, которые они будут украшать.
публичныйабстрактныйсортТоппингДекораторреализуетПицца{
публичный Нить описание(){
возвращаться"Неизвестная начинка";
}
}
Класс ToppingDecorator представляет класс декоратора в этом примере приложения. Теперь пиццерия может создавать множество разных начинок (или декораторов) с помощью класса ToppingDecorator. Допустим, пицца может иметь три разных вида начинки, а именно сыр, пепперони и грибы.
Сырная начинка
публичныйсортСыррасширяетТоппингДекоратор{
частный Пицца пицца;публичныйСыр(Пицца-пицца){
этот.пицца = пицца;
}@Override
публичный Нить описание(){
возвращаться пицца.описание() + ", Сырная начинка";
}
@Override
публичныйдвойнойрасходы(){
возвращатьсяпицца.расходы() + 2.50;
}
}
Топпинг Пепперони
публичныйсортПепперонирасширяетТоппингДекоратор{
частный Пицца пицца;публичныйПепперони(Пицца-пицца){
этот.пицца = пицца;
}@Override
публичный Нить описание(){
возвращаться пицца.описание() + ", Топпинг Пепперони";
}
@Override
публичныйдвойнойрасходы(){
возвращатьсяпицца.расходы() + 3.50;
}
}
Грибная начинка
публичныйсортГрибрасширяетТоппингДекоратор{
частный Пицца пицца;публичныйГриб(Пицца-пицца){
этот.пицца = пицца;
}
@Override
публичный Нить описание(){
возвращаться пицца.описание() + ", Грибная начинка";
}
@Override
публичныйдвойнойрасходы(){
возвращатьсяпицца.расходы() + 4.50;
}
}
Теперь у вас есть простое приложение, реализованное с использованием шаблона проектирования декоратора. Если клиент закажет пиццу на дрожжевой корочке с сыром и пепперони, тестовый код для этого сценария будет выглядеть следующим образом:
публичныйсортОсновной{
публичныйстатическийпустотаосновной(строка [] аргументы){
Пицца пицца1 = новый Дрожжевая коркаПицца();
пицца1 = новый Пепперони (пицца1);
пицца1 = новый Сыр (пицца1);
System.out.println (pizza1.description() + " $" + пицца1.стоимость());
}
}
Запуск этого кода приведет к следующему выводу в консоли:
Как видите, в выводе указывается тип пиццы и ее общая стоимость. Пицца начиналась как пицца с дрожжевой корочкой за 18 долларов, но с помощью шаблона декоратора приложение смогло добавить в пиццу новые функции и их соответствующую стоимость. Таким образом, придание пицце нового поведения без изменения существующего кода (пицца с дрожжевой корочкой).
С помощью шаблона декоратора вы также можете применять одно и то же поведение к объекту столько раз, сколько пожелаете. Если клиент заказывает пиццу со всем на ней и немного сыра, вы можете обновить основной класс следующим кодом, чтобы отразить это:
Пицца пицца2 = новый Дрожжевая коркаПицца();
пицца2 = новый Пепперони (пицца2);
пицца2 = новый Сыр (пицца2);
пицца2 = новый Сыр (пицца2);
пицца2 = новый Гриб (пицца2);System.out.println (pizza2.description() + " $" + пицца2.стоимость());
Обновленное приложение выведет в консоли следующий вывод:
Преимущества использования шаблона проектирования Decorator
Двумя основными преимуществами использования шаблона проектирования декоратора являются безопасность и гибкость. Шаблон декоратора позволяет разрабатывать более безопасный код, не вмешиваясь в уже существующий безопасный код. Вместо этого он расширяет существующий код посредством композиции. Эффективно предотвращает появление новых ошибок или непреднамеренных побочных эффектов.
Благодаря композиции разработчик также имеет большую гибкость при использовании шаблона декоратора. Вы можете реализовать новый декоратор в любое время, чтобы добавить новое поведение, не изменяя существующий код и не нарушая работу приложения.