Что такое паттерн Wrapper (Decorator) для компонента?

Ответ

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

Зачем он нужен? Для расширения возможностей объектов гибко и без изменения их исходного кода, следуя принципу открытости/закрытости.

Пример на Java:

// Базовый интерфейс компонента
interface Component {
    String operation();
}

// Конкретный компонент
class ConcreteComponent implements Component {
    @Override
    public String operation() {
        return "Базовая операция";
    }
}

// Базовый класс декоратора
abstract class Decorator implements Component {
    protected Component component;

    public Decorator(Component component) {
        this.component = component;
    }

    @Override
    public String operation() {
        return component.operation();
    }
}

// Конкретный декоратор A
class ConcreteDecoratorA extends Decorator {
    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    @Override
    public String operation() {
        return "[Доп. функциональность A] " + super.operation();
    }
}

// Конкретный декоратор B
class ConcreteDecoratorB extends Decorator {
    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    @Override
    public String operation() {
        return super.operation() + " [Доп. функциональность B]";
    }
}

// Использование
public class Main {
    public static void main(String[] args) {
        Component simple = new ConcreteComponent();
        System.out.println(simple.operation()); // Базовая операция

        Component decorated = new ConcreteDecoratorB(new ConcreteDecoratorA(simple));
        System.out.println(decorated.operation());
        // [Доп. функциональность A] Базовая операция [Доп. функциональность B]
    }
}

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

  • Гибкость: Большая, чем у наследования. Обёртки можно комбинировать динамически.
  • Принцип единой ответственности: Можно разделить монолитный класс на несколько простых декораторов.
  • Расширяемость: Новую функциональность можно добавить, создав новый класс-декоратор.

Недостатки:

  • Усложняет архитектуру, создавая множество мелких классов.
  • Может быть сложно сконфигурировать многоуровневые обёртки.

Ответ 18+ 🔞

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

Вот для этого и придумали декоратор, гений блядь! Суть проще пареной репы: ты не лезешь в кишки исходного объекта, а просто заворачиваешь его, как матрёшку, в другую обёртку. Эта обёртка имеет тот же самый интерфейс, что и оригинал, но делает чуть больше — например, логирует вызов, а потом делегирует выполнение внутрь, той самой «матрёшке» поменьше.

Смотри, вот тебе пример на Java, чтобы не быть голословным:

// Вот есть у нас интерфейс, скажем, Кофе. Просто кофе, блядь.
interface Coffee {
    String getDescription();
    double getCost();
}

// Конкретная реализация — простой эспрессо. Основа, хуле.
class Espresso implements Coffee {
    @Override
    public String getDescription() {
        return "Эспрессо";
    }

    @Override
    public double getCost() {
        return 100.0;
    }
}

// А вот и наша хитрая жопа — абстрактный декоратор.
// Он ТОЖЕ реализует интерфейс Coffee, но хранит ссылку на оборачиваемый кофе.
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();
    }
}

// Теперь конкретные декораторы — это как добавки к кофе.
class WithMilk extends CoffeeDecorator {
    public WithMilk(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", с молоком";
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 30.0;
    }
}

class WithSyrup extends CoffeeDecorator {
    private String syrupType;

    public WithSyrup(Coffee coffee, String syrupType) {
        super(coffee);
        this.syrupType = syrupType;
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", с сиропом (" + syrupType + ")";
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 50.0;
    }
}

// И вот, блядь, магия!
public class Main {
    public static void main(String[] args) {
        // Берём базовый кофе
        Coffee myCoffee = new Espresso();
        System.out.println(myCoffee.getDescription() + " за " + myCoffee.getCost() + " руб.");

        // Оборачиваем его в молоко
        myCoffee = new WithMilk(myCoffee);
        System.out.println(myCoffee.getDescription() + " за " + myCoffee.getCost() + " руб.");

        // Потом решаем, что мало — и добавляем сироп! Обёртка в обёртку, ебать!
        myCoffee = new WithSyrup(myCoffee, "Карамельный");
        System.out.println(myCoffee.getDescription() + " за " + myCoffee.getCost() + " руб.");

        // Можно и сходу собрать мега-кофе:
        Coffee megaCoffee = new WithSyrup(new WithMilk(new Espresso()), "Ореховый");
        System.out.println("Мега-напиток: " + megaCoffee.getDescription() + ". Цена: " + megaCoffee.getCost());
    }
}

Что получаем на выходе, ёпта? Эспрессо за 100.0 руб. Эспрессо, с молоком за 130.0 руб. Эспрессо, с молоком, с сиропом (Карамельный) за 180.0 руб. Мега-напиток: Эспрессо, с молоком, с сиропом (Ореховый). Цена: 180.0

В чём, сука, профит?

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

А где подвох, спросишь?

  • Много мелких классов. Если декораторов 20, то в коде можно просто захлебнуться, блядь.
  • Конфигурация объекта становится неочевидной. Когда у тебя обёртка в обёртке в обёртке, иногда сложно понять, что там внутри и в каком порядке выполняется. Отладка может превратиться в ёбучку.
  • Инициализация выглядит, как ёперный театр. new A(new B(new C(new D()))) — глаза сломаешь.

Короче, паттерн мощный, но как нож: если использовать с умом — порежешь проблему как масло, а если хвататься бездумно — отхватишь себе лишней сложности, в рот меня чих-пых.