Menu
Coddy logo textTech

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

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

challenge icon

Задание

Легко

Давайте создадим систему уведомлений с использованием шаблона Decorator! Вы создадите базовое уведомление, которое можно расширить различными каналами доставки: уведомлениями по электронной почте, SMS и push-уведомлениями, оборачивая decorators друг в друга. Это отражает работу реальных систем, где одно сообщение может одновременно отправляться по нескольким каналам.

Вы организуете код в четырех файлах:

  • Notifier.java: Define интерфейс Notifier, который служит интерфейсом component для вашего шаблона Decorator. Он должен Declare метод send(String message), обрабатывающий отправку уведомлений.
  • BasicNotifier.java: Create класс concrete component, который implements Notifier. Он представляет простейшую форму уведомления: alert внутри приложения. При вызове send он должен вывести In-App: [message].
  • NotifierDecorators.java: Постройте здесь иерархию Decorator. Начните с abstract класса NotifierDecorator, который implements Notifier и хранит ссылку на обернутый объект 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 с сообщением
        
    }
}
quiz iconПроверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор Java