Что такое паттерн проектирования «Декоратор» (Decorator)?

Ответ

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

Зачем он нужен? Когда наследование неудобно или невозможно (например, класс final), или когда нужно добавлять обязанности объекту гибко и комбинировать их в разных порядках.

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

// 1. Базовый компонент
interface Coffee {
    double getCost();
    String getDescription();
}

// 2. Конкретный компонент
class SimpleCoffee implements Coffee {
    @Override
    public double getCost() { return 1.0; }
    @Override
    public String getDescription() { return "Simple coffee"; }
}

// 3. Базовый декоратор (абстрактный)
abstract class CoffeeDecorator implements Coffee {
    protected final Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }
    // Делегирование вызовов обернутому объекту
    @Override
    public double getCost() { return decoratedCoffee.getCost(); }
    @Override
    public String getDescription() { return decoratedCoffee.getDescription(); }
}

// 4. Конкретные декораторы
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) { super(coffee); }

    @Override
    public double getCost() {
        return super.getCost() + 0.5; // Добавляем стоимость молока
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", milk";
    }
}

class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) { super(coffee); }

    @Override
    public double getCost() { return super.getCost() + 0.2; }
    @Override
    public String getDescription() { return super.getDescription() + ", sugar"; }
}

// 5. Использование
public class Main {
    public static void main(String[] args) {
        Coffee myCoffee = new SimpleCoffee();
        System.out.println(myCoffee.getDescription() + " $" + myCoffee.getCost());
        // Simple coffee $1.0

        myCoffee = new MilkDecorator(myCoffee); // Декорируем молоком
        myCoffee = new SugarDecorator(myCoffee); // Затем сахаром
        System.out.println(myCoffee.getDescription() + " $" + myCoffee.getCost());
        // Simple coffee, milk, sugar $1.7
    }
}

Ключевые преимущества:

  • Гибкость превыше наследования. Можно добавлять новые обязанности, не создавая иерархии подклассов.
  • Динамическое добавление и удаление. Обёртки можно добавлять и комбинировать во время выполнения.
  • Соблюдение принципа открытости/закрытости.

Классический пример в Java API: потоки ввода-вывода (java.io). Например, BufferedInputStream — это декоратор для FileInputStream, добавляющий буферизацию.

InputStream is = new BufferedInputStream(new FileInputStream("file.txt"));

Ответ 18+ 🔞

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

Нахуя это надо? Ну, например, когда наследование не катит — класс final, или когда тебе надо комбинировать фичи в разном порядке, и чтоб гибко, блядь.

Смотри, пример на Java (про кофе, классика же):

// 1. Базовый компонент — что угодно, что можно декорировать
interface Coffee {
    double getCost();
    String getDescription();
}

// 2. Конкретный компонент — просто кофе, голый
class SimpleCoffee implements Coffee {
    @Override
    public double getCost() { return 1.0; }
    @Override
    public String getDescription() { return "Simple coffee"; }
}

// 3. Базовый декоратор (абстрактный) — он и есть та самая обёртка
abstract class CoffeeDecorator implements Coffee {
    protected final Coffee decoratedCoffee; // Обёрнутый объект, его не трогаем

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }
    // Делегируем вызовы обёрнутому объекту по умолчанию
    @Override
    public double getCost() { return decoratedCoffee.getCost(); }
    @Override
    public String getDescription() { return decoratedCoffee.getDescription(); }
}

// 4. Конкретные декораторы — вот они, наши пиздюлины
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) { super(coffee); }

    @Override
    public double getCost() {
        return super.getCost() + 0.5; // Накинули полтинник за молоко
    }

    @Override
    public String getDescription() {
        return super.getDescription() + ", milk";
    }
}

class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) { super(coffee); }

    @Override
    public double getCost() { return super.getCost() + 0.2; }
    @Override
    public String getDescription() { return super.getDescription() + ", sugar"; }
}

// 5. Использование — вот где магия, ёпта!
public class Main {
    public static void main(String[] args) {
        Coffee myCoffee = new SimpleCoffee();
        System.out.println(myCoffee.getDescription() + " $" + myCoffee.getCost());
        // Выведет: Simple coffee $1.0

        // А теперь начинаем обёртывать, как душе угодно
        myCoffee = new MilkDecorator(myCoffee); // Завернули в молоко
        myCoffee = new SugarDecorator(myCoffee); // А потом ещё и в сахар
        System.out.println(myCoffee.getDescription() + " $" + myCoffee.getCost());
        // Выведет: Simple coffee, milk, sugar $1.7
    }
}

Чем это, блядь, круто?

  • Гибкость, а не наследование. Не надо плодить классы CoffeeWithMilkAndSugar, CoffeeWithDoubleSugarAndNoMilk — ёб твою мать, с ума сойти можно. Просто оборачивай что надо.
  • Динамически, на лету. Можешь добавлять и снимать обёртки прямо во время выполнения. Хочешь — с сахаром, надоело — без.
  • Принцип открытости/закрытости в действии. Классы закрыты для модификации, но открыты для расширения через эти самые декораторы.

Где это в жизни, блядь, видел? Да вон же, в Java IO! Это ж сплошные декораторы.

InputStream is = new BufferedInputStream(new FileInputStream("file.txt"));

BufferedInputStream — это декоратор для FileInputStream, который добавляет буферизацию. Без него читать файл — это как через соломинку воду пить, овердохуища времени уйдёт.