Введение

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

Банда четырех в своей книге Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования определила назначение шаблона декоратора следующим образом:

Намерение: динамически прикреплять к объекту дополнительные обязанности. Декораторы предоставляют гибкую альтернативу созданию подклассов для расширения функциональности.

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

Пора собрать 🍔

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

Способ сборки гамбургера варьируется. Согласно Burger King, существует 221 184 возможных способа заказать гамбургер. Вам понадобится как минимум 200 лет, чтобы съесть столько гамбургеров, если вы съедите один гамбургер на завтрак, обед и ужин! Возможности безграничны.

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

Шаблон

В подобном сценарии шаблон декоратора становится удобным. Первый шаг - определить контракт. Вы должны уметь ответить на вопрос. Что вам нужно, чтобы гамбургер мог делать при сборке гамбургера?

  • Добавить начинку
  • Добавить приправы
  • Получить общее количество калорий
  • Получить общую стоимость

У вас будет интерфейс с тем, что такое контракт для aHamburger. Все, что есть в гамбургере, будет в этом родительском классе. Каждый новый элемент, который вы добавляете к нему, в конечном итоге будет отдельным классом.

После этого ... во время выполнения вы начнете добавлять к нему дополнительные элементы. Нужен гамбургер с помидорами? Просто добавляйте помидоры во время выполнения!

Чтобы вы реализовали этот шаблон, нам нужно рассмотреть, какие классы и интерфейсы необходимы вам для реализации шаблона проектирования декоратора.

1 - Интерфейс. Первым делом нужно создать интерфейс, который определяет контракт для объектов, которые вы будете создавать. Если мы последуем примеру гамбургера, подумайте о том, что интерфейс гамбургера должен предоставлять другим классам. В этом случае, возможно, нам нужно показать, какие начинки мы добавили или каково общее количество калорий.

2 - Обычный класс, реализующий интерфейс. Этот класс в конечном итоге реализует все методы из интерфейса. Это будет простой гамбургер. Если вы сделаете простой гамбургер, что в нем должно быть? Скорее всего, вам нужно будет начать только с хлеба. Все остальное будет считаться начинкой.

3 - Абстрактный класс декоратора. Этот абстрактный класс также реализует интерфейс. Он позаботится о том, чтобы в качестве аргумента в своем конструкторе использовался простой объект-гамбургер. Этот класс будет действовать как прокси, он в конечном итоге делегирует вызовы функций простому гамбургеру.

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

Теперь, когда вы знаете, какие классы и интерфейсы необходимы для шаблона декоратора, вы можете продолжить и реализовать их. На следующей диаграмме представлены классы, которые вам необходимо реализовать.

Как это реализовать

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

public interface Hamburger {
    List<String> getToppings();
    double getTotalCalories();
}

Как только вы это сделаете, вы должны приступить к реализации класса для простого гамбургера. Как упоминалось ранее, в нем будет только хлеб.

public class PlainHamburger implements Hamburger {
    @Override
    public List<String> getToppings() {
        List<String> toppings = new ArrayList<>();
        toppings.add("Bread");
        return toppings;
    }
        
    @Override
    public double getTotalCalories() {
        return 100.0;
    }
}

Следующим шагом будет создание вашего класса декоратора. Как упоминалось ранее, этот класс примет в качестве аргумента в своем конструкторе простой гамбургер. Затем он делегирует все вызовы этому объекту.

public abstract class HamburgerDecorator implements Hamburger {
    private Hamburger h;
    
    public HamburgerDecorator(Hamburger h){
        this.h = h;
    }
    
    @Override
    public List<String> getToppings() {
        return this.h.getToppings();
    }
        
    @Override
    public double getTotalCalories() {
        return this.h.getTotalCalories();
    }
}

После того, как вы закончили с классом декоратора, пришло время реализовать ваши классы топпинга.

public class HamburgerWithLettuce extends HamburgerDecorator {
    public HamburgerWithLettuce(Hamburger h) {
        super(h);
    }
    @Override
    public List<String> getToppings() {
        List<String> toppings = super.getToppings();
        toppings.add("Lettuce");
        return toppings;
    }
    @Override
    public double getTotalCalories() {
        return 25.0 + super.getTotalCalories();
    }
}

Теперь давайте посмотрим, как будет выглядеть сборка гамбургера с точки зрения потребителя. Вы начнете с создания объекта типа PlainHamburger. После этого вы создадите объект типа HamburgerWithLettuce и передадите в качестве аргумента свой простой гамбургер.

Hamburger plainHamburger = new PlainHamburger();
Hamburger withLettuce = new HamburgerWithLettuce(plainHamburger);

Теперь у вас должна быть возможность расширить сборку гамбургера, добавив больше классов, представляющих каждую из начинок, которые вы хотите добавить в свой гамбургер. Достаточно просто, правда? Теперь следует иметь в виду, что для каждой начинки будет свой класс, поэтому нужно поддерживать больше кода. Как и во всем, есть свои плюсы и минусы, которые вам следует оценить. Наслаждаться!

Ресурсы

Гамма, Эрих. Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования (Adobe Reader). Pearson Education .