Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по 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

Паттерн Декоратор проявляет себя наилучшим образом, когда вам нужно добавить объектам новые обязанности, не создавая при этом огромного количества подклассов. Каждый декоратор независим, поэтому вы можете свободно комбинировать их, чтобы получить именно то сочетание поведений, которое вам нужно.

challenge icon

Задание

Легко

Давайте создадим систему уведомлений, используя паттерн Декоратор! Вы создадите базовое уведомление, которое можно дополнить различными каналами доставки — оповещениями по электронной почте, 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 у полностью декорированного уведомления с сообщением
        
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование