Статьи

Пример шаблона оформления декоратора

Эта статья является частью нашего курса Академии под названием « Шаблоны проектирования Java» .

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

1. Введение

Чтобы понять шаблон дизайна декоратора, давайте поможем пиццерии создать дополнительный калькулятор. Пользователь может попросить добавить дополнительную начинку к пицце, и наша задача — добавить добавку и повысить ее цену с помощью системы.

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

2. Что такое шаблон оформления декоратора

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

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

Объект Decorator предназначен для того же интерфейса, что и базовый объект. Это позволяет клиентскому объекту взаимодействовать с объектом Decorator точно таким же образом, как и с базовым фактическим объектом. Объект Decorator содержит ссылку на фактический объект. Объект Decorator получает все запросы (звонки) от клиента. В свою очередь, он передает эти вызовы базовому объекту. Объект Decorator добавляет некоторые дополнительные функции до или после пересылки запросов к базовому объекту. Это гарантирует, что дополнительные функциональные возможности могут быть добавлены к данному объекту извне во время выполнения без изменения его структуры.

Декоратор предотвращает распространение подклассов, что приводит к меньшей сложности и путанице. Легко добавить любую комбинацию возможностей. Одна и та же возможность может быть добавлена ​​дважды. Становится возможным иметь разные объекты декоратора для данного объекта одновременно. Клиент может выбрать, какие возможности он хочет, отправив сообщения соответствующему декоратору.

decorator_design_pattern_class_diagram_1

Рисунок 1 — Диаграмма классов

Составная часть

  • Определяет интерфейс для объектов, к которым можно динамически добавлять обязанности.

ConcreteComponent

  • Определяет объект, к которому могут быть прикреплены дополнительные обязанности.

декоратор

  • Поддерживает ссылку на объект Component и определяет интерфейс, который соответствует интерфейсу Компонента.

ConcreteDecorator

  • Добавляет обязанности к компоненту.

3. Реализация шаблона дизайна декоратора

Для простоты давайте создадим простой интерфейс Pizza который содержит только два метода.

1
2
3
4
5
6
7
package com.javacodegeeks.patterns.decoratorpattern;
 
public interface Pizza {
 
    public String getDesc();
    public double getPrice();
}

Метод getDesc используется для получения описания пиццы, а метод getPrice — для получения цены.

Ниже приведены два конкретных класса Pizza :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package com.javacodegeeks.patterns.decoratorpattern;
 
public class SimplyVegPizza implements Pizza{
 
    @Override
    public String getDesc() {
        return "SimplyVegPizza (230)";
    }
     
    @Override
    public double getPrice() {
        return 230;
    }
 
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package com.javacodegeeks.patterns.decoratorpattern;
 
public class SimplyNonVegPizza implements Pizza{
     
    @Override
    public String getDesc() {
        return "SimplyNonVegPizza (350)";
    }
 
    @Override
    public double getPrice() {
        return 350;
    }
 
}

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

01
02
03
04
05
06
07
08
09
10
package com.javacodegeeks.patterns.decoratorpattern;
 
public abstract class PizzaDecorator implements Pizza {
     
    @Override
    public String getDesc() {
        return "Toppings";
    }
 
}

Ниже приведены конкретные классы декоратора.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javacodegeeks.patterns.decoratorpattern;
 
public class Broccoli extends PizzaDecorator{
 
    private final Pizza pizza;
     
    public Broccoli(Pizza pizza){
        this.pizza = pizza;
    }
 
    @Override
    public String getDesc() {
        return pizza.getDesc()+", Broccoli (9.25)";
    }
 
 
    @Override
    public double getPrice() {
        return pizza.getPrice()+9.25;
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javacodegeeks.patterns.decoratorpattern;
 
public class Cheese extends PizzaDecorator{
 
    private final Pizza pizza;
     
    public Cheese(Pizza pizza){
        this.pizza = pizza;
    }
 
    @Override
    public String getDesc() {
        return pizza.getDesc()+", Cheese (20.72)";
    }
 
 
    @Override
    public double getPrice() {
        return pizza.getPrice()+20.72;
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javacodegeeks.patterns.decoratorpattern;
 
public class Chicken extends PizzaDecorator{
 
    private final Pizza pizza;
     
    public Chicken(Pizza pizza){
        this.pizza = pizza;
    }
 
    @Override
    public String getDesc() {
        return pizza.getDesc()+", Chicken (12.75)";
    }
 
 
    @Override
    public double getPrice() {
        return pizza.getPrice()+12.75;
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javacodegeeks.patterns.decoratorpattern;
 
public class FetaCheese extends PizzaDecorator{
 
    private final Pizza pizza;
     
    public FetaCheese(Pizza pizza){
        this.pizza = pizza;
    }
 
    @Override
    public String getDesc() {
        return pizza.getDesc()+", Feta Cheese (25.88)";
    }
 
 
    @Override
    public double getPrice() {
        return pizza.getPrice()+25.88;
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javacodegeeks.patterns.decoratorpattern;
 
public class GreenOlives extends PizzaDecorator{
 
    private final Pizza pizza;
     
    public GreenOlives(Pizza pizza){
        this.pizza = pizza;
    }
 
    @Override
    public String getDesc() {
        return pizza.getDesc()+", Green Olives (5.47)";
    }
 
 
    @Override
    public double getPrice() {
        return pizza.getPrice()+5.47;
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javacodegeeks.patterns.decoratorpattern;
 
public class Ham extends PizzaDecorator{
 
    private final Pizza pizza;
     
    public Ham(Pizza pizza){
        this.pizza = pizza;
    }
 
    @Override
    public String getDesc() {
        return pizza.getDesc()+", Ham (18.12)";
    }
 
 
    @Override
    public double getPrice() {
        return pizza.getPrice()+18.12;
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javacodegeeks.patterns.decoratorpattern;
 
public class Meat extends PizzaDecorator{
 
    private final Pizza pizza;
     
    public Meat(Pizza pizza){
        this.pizza = pizza;
    }
 
    @Override
    public String getDesc() {
        return pizza.getDesc()+", Meat (14.25)";
    }
 
 
    @Override
    public double getPrice() {
        return pizza.getPrice()+14.25;
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javacodegeeks.patterns.decoratorpattern;
 
public class RedOnions extends PizzaDecorator{
 
    private final Pizza pizza;
     
    public RedOnions(Pizza pizza){
        this.pizza = pizza;
    }
 
    @Override
    public String getDesc() {
        return pizza.getDesc()+", Red Onions (3.75)";
    }
 
 
    @Override
    public double getPrice() {
        return pizza.getPrice()+3.75;
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javacodegeeks.patterns.decoratorpattern;
 
public class RomaTomatoes extends PizzaDecorator{
 
    private final Pizza pizza;
     
    public RomaTomatoes(Pizza pizza){
        this.pizza = pizza;
    }
 
    @Override
    public String getDesc() {
        return pizza.getDesc()+", Roma Tomatoes (5.20)";
    }
 
 
    @Override
    public double getPrice() {
        return pizza.getPrice()+5.20;
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javacodegeeks.patterns.decoratorpattern;
 
public class Spinach extends PizzaDecorator{
 
    private final Pizza pizza;
     
    public Spinach(Pizza pizza){
        this.pizza = pizza;
    }
 
    @Override
    public String getDesc() {
        return pizza.getDesc()+", Spinach (7.92)";
    }
 
 
    @Override
    public double getPrice() {
        return pizza.getPrice()+7.92;
    }
 
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.javacodegeeks.patterns.decoratorpattern;
 
import java.text.DecimalFormat;
 
public class TestDecoratorPattern {
     
    public static void main(String[] args) {
         
        DecimalFormat dformat = new DecimalFormat("#.##");
        Pizza pizza = new SimplyVegPizza();
         
        pizza = new RomaTomatoes(pizza);
        pizza = new GreenOlives(pizza);
        pizza = new Spinach(pizza);
         
        System.out.println("Desc: "+pizza.getDesc());
        System.out.println("Price: "+dformat.format(pizza.getPrice()));
         
        pizza = new SimplyNonVegPizza();
         
        pizza = new Meat(pizza);
        pizza = new Cheese(pizza);
        pizza = new Cheese(pizza);
        pizza = new Ham(pizza);
         
        System.out.println("Desc: "+pizza.getDesc());
        System.out.println("Price: "+dformat.format(pizza.getPrice()));
    }
 
}

Приведенный выше код приведет к следующему выводу:

1
2
3
4
Desc: SimplyVegPizza (230), Roma Tomatoes (5.20), Green Olives (5.47), Spinach (7.92)
Price: 248.59
Desc: SimplyNonVegPizza (350), Meat (14.25), Cheese (20.72), Cheese (20.72), Ham (18.12)
Price: 423.81

В вышеприведенном классе мы сначала создали SimplyVegPizza а затем украсили его RomaTomatoes , GreenOlives и GreenOlives . Описание в выводе показывает начинки, добавленные в SimplyVegPizza и цена является суммой всех.

Мы сделали то же самое для SimplyNonVegPizza и добавили разные начинки. Обратите внимание, что вы можете украсить одно и то же более одного раза для объекта. В приведенном выше примере мы добавили cheese дважды; он также был добавлен в цену дважды, что видно на выходе.

Шаблон Design Decorator хорошо выглядит, когда вам нужно добавить дополнительную функциональность к объекту с его изменением во время выполнения. Но это приводит к множеству маленьких объектов. Дизайн, который использует Decorator, часто приводит к тому, что системы состоят из множества маленьких объектов, которые выглядят одинаково. Объекты отличаются только тем, как они взаимосвязаны, а не их классом или значением их переменных. Хотя эти системы легко настраиваются теми, кто их понимает, их может быть сложно изучить и отладить.

4. Когда использовать шаблон оформления декоратора

Используйте шаблон Decorator в следующих случаях:

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

5. Шаблон дизайна декоратора на Java

      • java.io.BufferedInputStream(InputStream)
      • java.io.DataInputStream(InputStream)
      • java.io.BufferedOutputStream(OutputStream)
      • java.util.zip.ZipOutputStream(OutputStream)
      • java.util.Collections#checked[List|Map|Set|SortedSet|SortedMap]()

6. Загрузите исходный код

Это был урок по шаблону оформления декоратора.

Вы можете скачать соответствующий исходный код здесь: DecoratorPattern-Project