«1. Обзор

Шаблон декоратора можно использовать для придания объекту дополнительных обязанностей статически или динамически. Декоратор предоставляет расширенный интерфейс для исходного объекта.

В реализации этого шаблона мы предпочитаем композицию наследованию, чтобы мы могли снова и снова сокращать накладные расходы на подклассы для каждого декоративного элемента. Рекурсия, связанная с этим дизайном, может использоваться для украшения нашего объекта столько раз, сколько нам нужно.

2. Пример шаблона декоратора

Предположим, у нас есть объект рождественской елки, и мы хотим его украсить. Украшение не меняет самого объекта; просто в дополнение к рождественской елке мы добавим некоторые элементы декора, такие как гирлянды, мишура, верхушки деревьев, пузыри и т. д.:

В этом сценарии мы будем следовать оригинальной Банде Четыре соглашения по дизайну и именованию. Сначала мы создадим интерфейс ChristmasTree и его реализацию:

public interface ChristmasTree {
    String decorate();
}

Реализация этого интерфейса будет выглядеть так:

public class ChristmasTreeImpl implements ChristmasTree {

    @Override
    public String decorate() {
        return "Christmas tree";
    }
}

Теперь мы создадим абстрактный класс TreeDecorator для этого дерева. Этот декоратор реализует интерфейс ChristmasTree, а также содержит тот же объект. Реализованный метод из того же интерфейса просто вызовет метод decor() из нашего интерфейса:

public abstract class TreeDecorator implements ChristmasTree {
    private ChristmasTree tree;
    
    // standard constructors
    @Override
    public String decorate() {
        return tree.decorate();
    }
}

Теперь мы создадим декоративный элемент. Эти декораторы расширят наш абстрактный класс TreeDecorator и модифицируют его метод decor() в соответствии с нашими требованиями:

public class BubbleLights extends TreeDecorator {

    public BubbleLights(ChristmasTree tree) {
        super(tree);
    }
    
    public String decorate() {
        return super.decorate() + decorateWithBubbleLights();
    }
    
    private String decorateWithBubbleLights() {
        return " with Bubble Lights";
    }
}

В этом случае верно следующее:

@Test
public void whenDecoratorsInjectedAtRuntime_thenConfigSuccess() {
    ChristmasTree tree1 = new Garland(new ChristmasTreeImpl());
    assertEquals(tree1.decorate(), 
      "Christmas tree with Garland");
     
    ChristmasTree tree2 = new BubbleLights(
      new Garland(new Garland(new ChristmasTreeImpl())));
    assertEquals(tree2.decorate(), 
      "Christmas tree with Garland with Garland with Bubble Lights");
}

Обратите внимание, что в первом объекте tree1, мы украшаем его только одной гирляндой, в то время как другой объект tree2 мы украшаем одним BubbleLights и двумя гирляндами. Этот шаблон дает нам возможность добавлять столько декораторов, сколько мы хотим во время выполнения.

4. Заключение

В этой статье мы рассмотрели шаблон проектирования декоратора. Это хороший выбор в следующих случаях:

    Когда мы хотим добавить, улучшить или даже удалить поведение или состояние объектов Когда мы просто хотим изменить функциональность одного объекта класса и оставить другие без изменений

Полный исходный код этого примера доступен на GitHub.