Паттерн «Декоратор»
Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy — урок 72 из 87.
Паттерн Декоратор — это структурный паттерн проектирования, который позволяет добавлять объектам новые варианты поведения, оборачивая их в специальные объекты, называемые декораторами. В отличие от наследования, которое добавляет поведение на этапе компиляции, декораторы добавляют функциональность динамически во время выполнения, не изменяя исходный класс.
Представьте себе кофейню, где вы начинаете с обычного кофе и можете добавлять в него такие добавки, как молоко, сахар или взбитые сливки. Каждая добавка «оборачивает» исходный кофе, добавляя свою стоимость и описание. Этот паттерн использует общий интерфейс, благодаря чему с декорированными объектами можно работать так же, как и с исходным:
interface Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}Декоратор хранит ссылку на обёрнутый объект и делегирует ему вызовы, добавляя при этом своё собственное поведение. Вы можете накладывать несколько декораторов друг на друга, чтобы комбинировать их возможности:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Простой кофе, молоко, сахар
System.out.println(order.getCost()); // 2.75Паттерн Декоратор проявляет себя наилучшим образом, когда вам нужно добавить объектам новые обязанности, не создавая при этом огромного количества подклассов. Каждый декоратор независим, поэтому вы можете свободно комбинировать их, чтобы получить именно то сочетание поведений, которое вам нужно.
Задание
ЛегкоДавайте создадим систему уведомлений, используя паттерн Декоратор! Вы создадите базовое уведомление, которое можно дополнить различными каналами доставки — оповещениями по электронной почте, SMS-оповещениями и push-уведомлениями — путем оборачивания декораторов друг в друга. Это имитирует реальные системы, где одно и то же сообщение может потребоваться отправить через несколько каналов одновременно.
Вы организуете свой код в четырех файлах:
Notifier.java: Определите интерфейсNotifier, который служит интерфейсом компонента для вашего паттерна декоратор. Он должен объявлять методsend(String message), который обрабатывает отправку уведомлений.BasicNotifier.java: Создайте конкретный класс компонента, реализующийNotifier. Это представляет собой простейшую форму уведомления — оповещение внутри приложения. При вызовеsendон должен выводить"In-App: " + message.NotifierDecorators.java: Постройте здесь иерархию декораторов. Начните с абстрактного классаNotifierDecorator, который реализуетNotifierи хранит ссылку на обернутый объектNotifier(передаваемый через конструктор).Затем создайте три конкретных декоратора, расширяющих
NotifierDecorator:EmailDecorator— при вызовеsendон сначала делегирует выполнение обернутому уведомлению, а затем выводит"Email: " + message.SMSDecorator— при вызовеsendон сначала делегирует выполнение обернутому уведомлению, а затем выводит"SMS: " + message.PushDecorator— при вызовеsendон сначала делегирует выполнение обернутому уведомлению, а затем выводит"Push: " + message.Main.java: Соберите вашу систему декораторов воедино! Вы получите два входных значения: сообщение уведомления (String) и список добавляемых каналов через запятую (String), например"email,sms"или"push,email,sms".Начните с
BasicNotifier. Затем, основываясь на входных данных о каналах, оберните его соответствующими декораторами в том порядке, в котором они появляются. Допустимые названия каналов:"email","sms"и"push".Наконец, вызовите
sendс вашим сообщением на полностью декорированном объекте уведомления.
Вы получите два входных значения по порядку: сообщение для отправки (String) и каналы для включения (String, через запятую).
Например, при входных данных "Server is down!" и "email,push" ваш вывод будет следующим:
In-App: Server is down!
Email: Server is down!
Push: Server is down!Обратите внимание, как каждый декоратор добавляет свое собственное поведение, сохраняя при этом исходное уведомление. Порядок оборачивания определяет порядок вывода — сначала выполняется самый внутренний компонент (BasicNotifier), а затем каждый декоратор добавляет свой слой. Вы можете комбинировать любые каналы, не создавая отдельные классы для каждой комбинации!
Шпаргалка
Паттерн Декоратор (Decorator Pattern) — это структурный паттерн проектирования, который позволяет добавлять объектам новые обязанности, оборачивая их в объекты-декораторы. Он расширяет функциональность динамически во время выполнения, не изменяя исходный класс.
Паттерн использует общий интерфейс, благодаря чему с декорированными объектами можно работать так же, как и с исходным объектом.
Базовая структура
Определите интерфейс компонента:
interface Coffee {
String getDescription();
double getCost();
}Создайте конкретный компонент, реализующий этот интерфейс:
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}Создайте абстрактный класс декоратора, который реализует тот же интерфейс и хранит ссылку на оборачиваемый объект:
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}Создайте конкретные декораторы, расширяющие абстрактный декоратор:
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}Использование декораторов
Комбинируйте несколько декораторов для объединения возможностей:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Simple Coffee, Milk, Sugar
System.out.println(order.getCost()); // 2.75Декоратор делегирует вызовы обернутому объекту, добавляя при этом собственное поведение. Каждый декоратор независим, и их можно свободно комбинировать друг с другом.
Попробуйте сами
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Прочитать сообщение для отправки
String message = scanner.nextLine();
// Прочитать список каналов, разделенных запятыми (например, "email,sms" или "push,email,sms")
String channelsInput = scanner.nextLine();
// TODO: Начать с BasicNotifier
// TODO: Разделить входную строку каналов по запятой
// TODO: Пройти циклом по каждому каналу и обернуть уведомление соответствующим декоратором
// Допустимые каналы: "email", "sms", "push"
// TODO: Вызвать метод send у полностью декорированного уведомления с сообщением
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово thisМетодыПоля (атрибуты)Метод-конструкторПерегрузка конструкторовИтоги — Простой калькулятор4Наследование
Основы наследования (extends)Ключевое слово superПереопределение методов (@Override)Цепочка конструкторовКласс ObjectОдиночное и многоуровневое наследованиеПочему нет множественного наследования классовИтоги — Иерархия сотрудников7Специальные методы и класс Object
Метод toString()equals() и hashCode()Метод clone()compareTo() и ComparableИнтерфейс ComparatorПовторение — Кастомная сортировка2Модификаторы доступа и инкапсуляция
Обзор уровней доступаМетоды геттеры и сеттерыСокрытие данныхКлючевое слово finalИтоги — Менеджер банковского счета5Полиморфизм
Основы перегрузки методовПереопределение методов (Run-Time)Upcasting и DowncastingОператор instanceofАбстрактные классы и методыИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияАгрегация против композицииВнутренние, вложенные и анонимные классыEnums и методы перечисленийRecords (Java 16+)Sealed-классы (Java 17+)11Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy3Свойства класса и статические члены
Переменные экземпляра и статические переменныеСтатические методыСтатические блокиКонстанты (static final)Итоги: Счетчик и утилиты6Интерфейсы и абстрактные классы
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовDefault и Static методы в интерфейсахАбстрактные классы vs ИнтерфейсыФункциональные интерфейсыИтоги — Платежная система9Обобщения
Введение в обобщенияОбобщенные классыОбобщенные методыОграниченные параметры типаСимволы подстановки (Wildcards: ?, extends, super)Итоги — Обобщенный контейнер12Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»