Что такое паттерн Decorator и как он применяется?

Ответ

Decorator (Декоратор) — это структурный паттерн проектирования, который позволяет динамически добавлять новую функциональность объектам, оборачивая их в объекты-декораторы, не изменяя их исходный код. Это гибкая альтернатива наследованию для расширения поведения.

Принцип работы:

  1. Создается общий интерфейс для основного объекта и декораторов.
  2. Базовый класс реализует этот интерфейс.
  3. Класс-декоратор также реализует этот интерфейс и содержит ссылку на оборачиваемый объект.
  4. Декоратор делегирует вызовы обернутому объекту, добавляя свою логику до или после.

Пример на Java (Модель кофе с добавками):

// 1. Общий интерфейс
interface Coffee {
    String getDescription();
    double getCost();
}

// 2. Базовый компонент
class SimpleCoffee implements Coffee {
    public String getDescription() { return "Простой кофе"; }
    public double getCost() { return 100.0; }
}

// 3. Базовый абстрактный декоратор (необязательно, но рекомендуется)
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;
    public CoffeeDecorator(Coffee coffee) { this.decoratedCoffee = coffee; }
    public String getDescription() { return decoratedCoffee.getDescription(); }
    public double getCost() { return decoratedCoffee.getCost(); }
}

// 4. Конкретные декораторы
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) { super(coffee); }
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", молоко";
    }
    public double getCost() {
        return decoratedCoffee.getCost() + 30.0;
    }
}

class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) { super(coffee); }
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", сахар";
    }
    public double getCost() {
        return decoratedCoffee.getCost() + 5.0;
    }
}

// 5. Использование
public class Main {
    public static void main(String[] args) {
        Coffee myCoffee = new SimpleCoffee();
        myCoffee = new MilkDecorator(myCoffee);
        myCoffee = new SugarDecorator(myCoffee);
        myCoffee = new SugarDecorator(myCoffee); // Двойной сахар

        System.out.println(myCoffee.getDescription()); // "Простой кофе, молоко, сахар, сахар"
        System.out.println("Цена: " + myCoffee.getCost()); // 140.0
    }
}

Преимущества:

  • Гибкость: Позволяет добавлять и комбинировать обязанности во время выполнения.
  • Принцип открытости/закрытости (OCP): Код закрыт для модификации, но открыт для расширения.
  • Избегание «взрывного» роста классов: В отличие от наследования, где для каждой комбинации нужен отдельный подкласс.

Недостатки:

  • Множество мелких классов: Может усложнить структуру программы.
  • Сложность инициализации: Клиентский код должен собирать цепочку декораторов, что может быть неочевидно.
  • Сложность конфигурации: Трудно удалить конкретный декоратор из середины цепочки.

Применение в Java: Классический пример — потоки ввода-вывода (BufferedReader, GZIPInputStream), которые оборачивают другие потоки.

Ответ 18+ 🔞

А, паттерн Декоратор! Это ж как раз про то, как натянуть на старую идею новые прибамбасы, не переписывая всё к хуям собачьим. Представь себе, ты купил базовую тачку, а потом начинаешь её тюнинговать: спойлер нахуячить, подсветку под капот, сабвуфер в багажник. Так вот, Декоратор — это и есть ваш официальный тюнинг-ателье для объектов, без необходимости залезать в заводскую прошивку двигателя.

Как эта штука работает, если без соплей:

  1. Берёшь какой-нибудь главный интерфейс — это как список того, что твоя штука вообще умеет делать. Все, и базовая модель, и все обвесы, должны его соблюдать, иначе пиздец и несовместимость.
  2. Делаешь голый, нищебродский базовый класс, который просто умеет делать самое простое.
  3. Потом создаёшь классы-обёртки (декораторы). Каждый такой декоратор — как новый аксессуар. Он ВНУТРИ СЕБЯ хранит ссылку на то, что он оборачивает (ту самую тачку или уже обёрнутую тачку), и когда у него спрашивают «а что ты умеешь?», он сначала спрашивает у того, что внутри, а потом добавляет от себя: «Ну, он умеет ехать... А Я ЕЩЁ И ПРЕДОХРАНИТЕЛЬ СТЕКЛОПОДЪЁМНИКОВ СДЕЛАЛ!».
  4. И так можно накручивать обёртки одну на другую, как матрёшку, пока не получится тот самый перегруженный тазик с дискотекой.

Смотри, как это выглядит в коде на примере кофе, который мы все так любим переобвешивать:

// 1. Это наш главный договор. Все, кто хочет быть "кофе", должны уметь назвать себя и сказать цену.
interface Coffee {
    String getDescription();
    double getCost();
}

// 2. А это — голый, стыдный, базовый кофе. Делает минимум.
class SimpleCoffee implements Coffee {
    public String getDescription() { return "Простой кофе"; }
    public double getCost() { return 100.0; }
}

// 3. Абстрактный декоратор. Он как бы говорит: "Все обвесы, парни, делайте через меня, чтобы не повторяться".
// Он тоже умеет всё то же, что и кофе, но просто передаёт запросы тому, кого он оборачивает.
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee; // Вот эта ссылка на обёрнутую хуйню — самое главное!
    public CoffeeDecorator(Coffee coffee) { this.decoratedCoffee = coffee; }
    public String getDescription() { return decoratedCoffee.getDescription(); }
    public double getCost() { return decoratedCoffee.getCost(); }
}

// 4. А вот конкретные обвесы! Молоко, например.
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) { super(coffee); }
    public String getDescription() {
        // Спрашиваем у того, что внутри: "ты кто?", а потом добавляем своё.
        return decoratedCoffee.getDescription() + ", молоко";
    }
    public double getCost() {
        // Берём цену того, что внутри, и накручиваем сверху свою маржу.
        return decoratedCoffee.getCost() + 30.0;
    }
}

// Ещё один обвес — сахар. Их можно накрутить несколько, ёпта!
class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) { super(coffee); }
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", сахар";
    }
    public double getCost() {
        return decoratedCoffee.getCost() + 5.0;
    }
}

// 5. Ну и собираем нашу франкенштейн-чашку.
public class Main {
    public static void main(String[] args) {
        Coffee myCoffee = new SimpleCoffee(); // Начали с голого кофе.
        myCoffee = new MilkDecorator(myCoffee); // Обернули в молоко.
        myCoffee = new SugarDecorator(myCoffee); // Обернули в сахар.
        myCoffee = new SugarDecorator(myCoffee); // ОБЕРНУЛИ ЕЩЁ РАЗ В САХАР, Я ЖЕ ЛЮБЛЮ ПОСЛАЩЕ!

        System.out.println(myCoffee.getDescription()); // Выведет: "Простой кофе, молоко, сахар, сахар"
        System.out.println("Цена: " + myCoffee.getCost()); // 100 + 30 + 5 + 5 = 140.0
    }
}

Чем это, блядь, хорошо:

  • Гибкость — овердохуища. Хочешь новую фичу — написал один класс-декоратор и оборачивай что угодно. Не надо плодить классы-уроды вроде CoffeeWithMilkAndDoubleSugarButWithoutCinnamon.
  • Принцип «не лезь внутрь, если можно прикрутить сверху». Не трогаешь старый, отлаженный код, а просто добавляешь новое поведение обёрткой. Красота!
  • Избегаешь взрыва мозга от количества классов. Представь, сколько бы классов-потомков пришлось сделать через наследование для всех комбинаций сиропов, видов молока и посыпок. Пиздец бы наступил.

А чем это, сука, не очень:

  • Мелких классов становится — как собак нерезаных. Каждая мелкая обязанность — свой класс. Может получиться каша.
  • Собрать такую цепочку с нуля — ещё то удовольствие. Клиентский код превращается в конструктор: взял базу, обернул в одно, обернул в другое... Неочевидно сходу.
  • Выковыривать конкретный декоратор из середины цепочки — задача для садомазохиста. Придётся разматывать всю эту хуйню обратно.

Где это в жизни, блядь, встречается? Да везде! Самый ярый пример в Java — это потоки ввода-вывода (java.io). Ты берёшь простой FileInputStream, а потом оборачиваешь его в BufferedInputStream для буферизации, а тот, если хочешь, можешь запихнуть в GZIPInputStream для распаковки. Каждый новый поток — декоратор для предыдущего. Вот так-то, мать его.