Ответ
Паттерн 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())))— глаза сломаешь.
Короче, паттерн мощный, но как нож: если использовать с умом — порежешь проблему как масло, а если хвататься бездумно — отхватишь себе лишней сложности, в рот меня чих-пых.