Паттерн «Декоратор»
Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy. Урок 72 из 87.
Паттерн Decorator — это структурный шаблон проектирования, который позволяет добавлять новые виды поведения объектам, оборачивая их в специальные объекты, называемые decorator. В отличие от наследования, которое добавляет поведение во время компиляции, decorator динамически добавляют функциональность во время выполнения, не изменяя исходный class.
Представь кофейню, где ты начинаешь с базового coffee и можешь добавлять такие дополнения, как молоко, сахар или взбитые сливки. Каждое дополнение оборачивает исходный coffee, добавляя его стоимость и описание. В этом шаблоне используется общий интерфейс, благодаря которому с декорированными объектами можно обращаться так же, как с исходным:
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;
}
}decorator хранит ссылку на обёрнутый объект и делегирует ему вызовы, добавляя собственное поведение. Можно объединять функции, используя несколько decorator:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Простой кофе, Молоко, Сахар
System.out.println(order.getCost()); // 2.75Паттерн Decorator особенно полезен, когда нужно добавлять объектам обязанности, не создавая огромное количество подклассов. Каждый decorator независим, поэтому их можно свободно комбинировать, создавая именно то сочетание вариантов поведения, которое вам нужно.
Задание
ЛегкоДавайте создадим систему уведомлений с использованием шаблона Decorator! Вы создадите базовое уведомление, которое можно расширить различными каналами доставки: уведомлениями по электронной почте, SMS и push-уведомлениями, оборачивая decorators друг в друга. Это отражает работу реальных систем, где одно сообщение может одновременно отправляться по нескольким каналам.
Вы организуете код в четырех файлах:
Notifier.java: Define интерфейсNotifier, который служит интерфейсом component для вашего шаблона Decorator. Он должен Declare методsend(String message), обрабатывающий отправку уведомлений.BasicNotifier.java: Create класс concrete component, который implementsNotifier. Он представляет простейшую форму уведомления: alert внутри приложения. При вызовеsendон должен вывестиIn-App: [message].NotifierDecorators.java: Постройте здесь иерархию Decorator. Начните с abstract классаNotifierDecorator, который implementsNotifierи хранит ссылку на обернутый объектNotifier(передаваемый через его constructor).Затем создайте три concrete decorators, расширяющих
NotifierDecorator:EmailDecorator— при вызовеsendсначала передает вызов обернутому notifier, затем выводитEmail: [message].SMSDecorator— при вызовеsendсначала передает вызов обернутому notifier, затем выводитSMS: [message].PushDecorator— при вызовеsendсначала передает вызов обернутому notifier, затем выводитPush: [message].Main.java: Объедините вашу систему Decorator! Вы получите два входных значения: сообщение уведомления (String) и разделенный запятыми список channels для добавления (String), например"email,sms"или"push,email,sms".Начните с
BasicNotifier. Затем, основываясь на входном значении channels, оберните его соответствующими decorators в порядке их появления. Допустимые имена channels:email,smsиpush.Наконец, вызовите
sendс вашим сообщением у fully decorated notifier.
Вы получите два входных значения по порядку: сообщение для отправки (String) и channels для включения (String, разделенные запятыми).
Например, если входные значения — Server is down! и email,push, результатом будет:
In-App: Server is down!
Email: Server is down!
Push: Server is down!Обратите внимание, как каждый decorator добавляет собственное поведение, сохраняя исходное уведомление. Порядок оборачивания определяет порядок вывода: самый внутренний component (BasicNotifier) выполняется первым, затем каждый decorator добавляет свой слой. Вы можете комбинировать любые channels, не создавая отдельные классы для каждой комбинации!
Попробуйте сами
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: Пройти по каждому каналу и обернуть notifier соответствующим декоратором
// Допустимые каналы: "email", "sms", "push"
// TODO: Вызвать send у полностью декорированного notifier с сообщением
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»Потренируйтесь самостоятельно: Онлайн-компилятор Java