Поймите «Шаблон декоратора» на примере реального мира - PullRequest
157 голосов
/ 25 апреля 2010

Я изучал Шаблон декоратора , как описано в GOF .

Пожалуйста, помогите мне понять Шаблон декоратора . Может ли кто-нибудь привести пример использования того, где это полезно в реальном мире?

Ответы [ 14 ]

217 голосов
/ 25 апреля 2010

Шаблон декоратора достигает единственной цели динамического добавления ответственность перед любым объектом.

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

А вот и шаблон декоратора.

В соответствии с шаблоном декоратора вы будете реализовывать начинки, так как декораторы будут украшать пиццы. Практически каждый клиент захочет получить начинки по своему желанию, а окончательная сумма счета будет состоять из базовой пиццы и дополнительно заказанной начинки. Каждый топ-декоратор будет знать о пицце, которую он украшает, и о ее цене. Метод GetPrice () объекта Topping возвращает совокупную цену как пиццы, так и начинки.

EDIT

Вот пример кода объяснения выше.

public abstract class BasePizza
{
    protected double myPrice;

    public virtual double GetPrice()
    {
        return this.myPrice;
    }
}

public abstract class ToppingsDecorator : BasePizza
{
    protected BasePizza pizza;
    public ToppingsDecorator(BasePizza pizzaToDecorate)
    {
        this.pizza = pizzaToDecorate;
    }

    public override double GetPrice()
    {
        return (this.pizza.GetPrice() + this.myPrice);
    }
}

class Program
{
    [STAThread]
    static void Main()
    {
        //Client-code
        Margherita pizza = new Margherita();
        Console.WriteLine("Plain Margherita: " + pizza.GetPrice().ToString());

        ExtraCheeseTopping moreCheese = new ExtraCheeseTopping(pizza);
        ExtraCheeseTopping someMoreCheese = new ExtraCheeseTopping(moreCheese);
        Console.WriteLine("Plain Margherita with double extra cheese: " + someMoreCheese.GetPrice().ToString());

        MushroomTopping moreMushroom = new MushroomTopping(someMoreCheese);
        Console.WriteLine("Plain Margherita with double extra cheese with mushroom: " + moreMushroom.GetPrice().ToString());

        JalapenoTopping moreJalapeno = new JalapenoTopping(moreMushroom);
        Console.WriteLine("Plain Margherita with double extra cheese with mushroom with Jalapeno: " + moreJalapeno.GetPrice().ToString());

        Console.ReadLine();
    }
}

public class Margherita : BasePizza
{
    public Margherita()
    {
        this.myPrice = 6.99;
    }
}

public class Gourmet : BasePizza
{
    public Gourmet()
    {
        this.myPrice = 7.49;
    }
}

public class ExtraCheeseTopping : ToppingsDecorator
{
    public ExtraCheeseTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 0.99;
    }
}

public class MushroomTopping : ToppingsDecorator
{
    public MushroomTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 1.49;
    }
}

public class JalapenoTopping : ToppingsDecorator
{
    public JalapenoTopping(BasePizza pizzaToDecorate)
        : base(pizzaToDecorate)
    {
        this.myPrice = 1.49;
    }
}
30 голосов
/ 25 апреля 2010

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

// create a message object
var message = {
    text: "Lorem ipsum dolor sit amet, consectetur adipisicing elit..."
};

// add logging behavior to the message object dynamically
message.log = function() {
    console.log(this.text);
};

// use the newly added behavior to log text
​message.log();​ // Loren ipsum...​​​​​​​​​​​​​​​
16 голосов
/ 26 апреля 2010

Стоит отметить, что модель ввода-вывода Java основана на шаблоне декоратора. Расстановка этого считывателя поверх этого считывателя поверх ... - это настоящий пример декоратора.

7 голосов
/ 01 декабря 2014

Пример. Сценарий. Допустим, вы пишете модуль шифрования. Это шифрование может зашифровать чистый файл, используя DES - стандарт шифрования данных. Аналогично, в системе вы можете использовать шифрование как стандарт шифрования AES - Advance. Также у вас может быть комбинация шифрования - сначала DES, затем AES. Или вы можете иметь сначала AES, затем DES.

Дискуссия. Как вы справитесь с этой ситуацией? Вы не можете продолжать создавать объект таких комбинаций - например - AES и DES - всего 4 комбинации. Таким образом, вам нужно иметь 4 отдельных объекта. Это будет усложняться по мере увеличения типа шифрования.

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

Вот решение - на C ++.

Во-первых, вам нужен базовый класс - фундаментальная единица стека. Вы можете думать как основание стека. В этом примере это чистый файл. Будем всегда следовать полиморфизму. Сделайте сначала интерфейсный класс этой фундаментальной единицы. Таким образом, вы можете реализовать его как хотите. Кроме того, вам не нужно думать о зависимости при включении этого фундаментального модуля.

Вот класс интерфейса -

class IclearData
{
public:

    virtual std::string getData() = 0;
    virtual ~IclearData() = 0;
};

IclearData::~IclearData()
{
    std::cout<<"Destructor called of IclearData"<<std::endl;
}

Теперь реализуйте этот интерфейсный класс -

class clearData:public IclearData
{
private:

    std::string m_data;

    clearData();

    void setData(std::string data)
        {
            m_data = data;
        }

public:

    std::string getData()
    {
        return m_data;
    }

    clearData(std::string data)
    {
        setData(data);
    }

    ~clearData()
    {
        std::cout<<"Destructor of clear Data Invoked"<<std::endl;
    }

};

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

class encryptionDecorator: public IclearData
{

protected:
    IclearData *p_mclearData;

    encryptionDecorator()
    {
      std::cout<<"Encryption Decorator Abstract class called"<<std::endl;
    }

public:

    std::string getData()
    {
        return p_mclearData->getData();
    }

    encryptionDecorator(IclearData *clearData)
    {
        p_mclearData = clearData;
    }

    virtual std::string showDecryptedData() = 0;

    virtual ~encryptionDecorator() = 0;

};

encryptionDecorator::~encryptionDecorator()
{
    std::cout<<"Encryption Decorator Destructor called"<<std::endl;
}

Теперь давайте создадим класс декоратора бетона - Тип шифрования - AES -

const std::string aesEncrypt = "AES Encrypted ";

class aes: public encryptionDecorator
{

private:

    std::string m_aesData;

    aes();

public:

    aes(IclearData *pClearData): m_aesData(aesEncrypt)
    {
        p_mclearData = pClearData;
        m_aesData.append(p_mclearData->getData());
    }

    std::string getData()
        {
            return m_aesData;
        }

    std::string showDecryptedData(void)
    {
        m_aesData.erase(0,m_aesData.length());
        return m_aesData;
    }

};

Теперь предположим, что тип декоратора - DES -

const std :: string desEncrypt = "DES Encrypted";

class des: public encryptionDecorator
{

private:

    std::string m_desData;

    des();

public:

    des(IclearData *pClearData): m_desData(desEncrypt)
    {
        p_mclearData = pClearData;
        m_desData.append(p_mclearData->getData());
    }

    std::string getData(void)
        {
            return m_desData;
        }

    std::string showDecryptedData(void)
    {
        m_desData.erase(0,desEncrypt.length());
        return m_desData;
    }

};

Давайте создадим код клиента для использования этого класса декоратора -

int main()
{
    IclearData *pData = new clearData("HELLO_CLEAR_DATA");

    std::cout<<pData->getData()<<std::endl;


    encryptionDecorator *pAesData = new aes(pData);

    std::cout<<pAesData->getData()<<std::endl;

    encryptionDecorator *pDesData = new des(pAesData);

    std::cout<<pDesData->getData()<<std::endl;

    /** unwind the decorator stack ***/
    std::cout<<pDesData->showDecryptedData()<<std::endl;

    delete pDesData;
    delete pAesData;
    delete pData;

    return 0;
}

Вы увидите следующие результаты -

HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Abstract class called
DES Encrypted AES Encrypted HELLO_CLEAR_DATA
AES Encrypted HELLO_CLEAR_DATA
Encryption Decorator Destructor called
Destructor called of IclearData
Encryption Decorator Destructor called
Destructor called of IclearData
Destructor of clear Data Invoked
Destructor called of IclearData

Вот диаграмма UML - ее представление в классе. В случае, если вы хотите пропустить код и сосредоточиться на аспекте дизайна.

enter image description here

4 голосов
/ 08 августа 2017

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

Лучший пример - классы InputStream и OutputStream в пакете java.io

    File file=new File("target","test.txt");
    FileOutputStream fos=new FileOutputStream(file);
    BufferedOutputStream bos=new BufferedOutputStream(fos);
    ObjectOutputStream oos=new ObjectOutputStream(bos);


    oos.write(5);
    oos.writeBoolean(true);
    oos.writeBytes("decorator pattern was here.");


//... then close the streams of course.
3 голосов
/ 21 мая 2017

Я широко использовал шаблон Decorator на своей работе. Я написал в своем блоге сообщение о том, как использовать его для ведения журнала.

3 голосов
/ 22 июня 2013

Что такое шаблон проектирования Decorator в Java.

Формальное определение шаблона Decorator из книги GoF (Шаблоны проектирования: элементы многоразового объектно-ориентированного программного обеспечения, 1995, Pearson Education, Inc. Publishing as Pearson Addison Wesley) говорит, что вы можете

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

Допустим, у нас есть пицца, и мы хотим украсить ее такими начинками, как куриный масала, лук и сыр моцарелла. Давайте посмотрим, как реализовать это в Java ...

Программа для демонстрации того, как реализовать шаблон проектирования Decorator в Java.

Pizza.java:

<!-- language-all: lang-html -->

package com.hubberspot.designpattern.structural.decorator;

public class Pizza {

public Pizza() {

}

public String description(){
    return "Pizza";
}

}



package com.hubberspot.designpattern.structural.decorator;

public abstract class PizzaToppings extends Pizza {

public abstract String description();

}

package com.hubberspot.designpattern.structural.decorator;

public class ChickenMasala extends PizzaToppings {

private Pizza pizza;

public ChickenMasala(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + " with chicken masala, ";
}

}



package com.hubberspot.designpattern.structural.decorator;

public class MozzarellaCheese extends PizzaToppings {

private Pizza pizza;

public MozzarellaCheese(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + "and mozzarella cheese.";
}
}



package com.hubberspot.designpattern.structural.decorator;

public class Onion extends PizzaToppings {

private Pizza pizza;

public Onion(Pizza pizza) {
    this.pizza = pizza;
}

@Override
public String description() {
    return pizza.description() + "onions, ";
}

}



package com.hubberspot.designpattern.structural.decorator;

public class TestDecorator {

public static void main(String[] args) {

    Pizza pizza = new Pizza();

    pizza = new ChickenMasala(pizza);
    pizza = new Onion(pizza);
    pizza = new MozzarellaCheese(pizza);

    System.out.println("You're getting " + pizza.description());

}

}
2 голосов
/ 29 июля 2018

Шаблон декоратора достигает единственной цели , динамически добавляя обязанности к любому объекту .

Модель ввода-вывода Java основана на шаблоне декоратора.

Java IO as decorator pattern

2 голосов
/ 30 мая 2016

декоратор:

  1. Добавить поведение к объекту во время выполнения . Наследование является ключом для достижения этой функциональности, что является как преимуществом, так и недостатком этого шаблона.
  2. Улучшает поведение интерфейса.
  3. Декоратор можно рассматривать как вырожденный Композитный только с одним компонентом. Однако декоратор добавляет дополнительные обязанности - он не предназначен для агрегирования объектов.
  4. Класс Decorator объявляет композиционные отношения с интерфейсом LCD (самый низкий знаменатель класса), и этот элемент данных инициализируется в своем конструкторе.
  5. Декоратор позволяет добавлять обязанности к объектам без подклассов

Подробнее см. В статье sourcemaking .

Decorator (Abstract) : это абстрактный класс / интерфейс, который реализует интерфейс компонента. Содержит интерфейс компонента. В отсутствие этого класса вам понадобится много подклассов ConcreteDecorator для разных комбинаций. Композиция компонента уменьшает ненужные подклассы.

Пример JDK:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("a.txt")));
while(bis.available()>0)
{
        char c = (char)bis.read();
        System.out.println("Char: "+c);;
}

Посмотрите на вопрос SE ниже для UML-диаграммы и примеров кода.

Шаблон декоратора для ввода / вывода

Полезные статьи:

journaldev

википедии

Пример реального слова шаблона Decorator: объяснение VendingMachineDecorator @

Когда использовать шаблон декоратора?

Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
beverage.decorateBeverage();

beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
beverage.decorateBeverage();

В приведенном выше примере чай или кофе (напиток) украшены сахаром и лимоном.

2 голосов
/ 23 марта 2015

Шаблон декоратора позволяет динамически добавлять поведение к объектам.

Давайте рассмотрим пример, в котором вам нужно создать приложение, которое рассчитывает цену различных видов гамбургеров. Вам нужно обрабатывать разные варианты гамбургеров, например, «большой» или «с сыром», каждый из которых имеет цену относительно основного гамбургера. Например. добавьте 10 долларов за бургер с сыром, добавьте 15 долларов за большой бургер и т. д.

В этом случае у вас может возникнуть соблазн создать подклассы для их обработки. Мы можем выразить это в Ruby как:

class Burger
  def price
    50
  end
end

class BurgerWithCheese < Burger
  def price
    super + 15
  end
end

В приведенном выше примере класс BurgerWithCheese наследуется от Burger и переопределяет метод цен, добавляя $ 15 к цене, определенной в суперклассе. Вы также создадите класс LargeBurger и определите цену относительно Burger. Но вам также нужно определить новый класс для комбинации «большой» и «с сыром».

Теперь, что произойдет, если нам нужно подать "гамбургер с картошкой фри"? У нас уже есть 4 класса для обработки этих комбинаций, и нам нужно будет добавить еще 4 класса для обработки всех комбинаций из 3 свойств - «большой», «с сыром» и «с картофелем фри». Нам нужно 8 классов сейчас. Добавьте другое свойство, и нам понадобится 16. Это будет расти как 2 ^ n.

Вместо этого давайте попробуем определить BurgerDecorator, который принимает объект Burger:

class BurgerDecorator
  def initialize(burger)
    self.burger = burger
  end
end

class BurgerWithCheese < BurgerDecorator
  def price
    self.burger.price + 15
  end
end

burger = Burger.new
cheese_burger = BurgerWithCheese.new(burger)
cheese_burger.price   # => 65

В приведенном выше примере мы создали класс BurgerDecorator, от которого наследуется класс BurgerWithCheese. Мы также можем представить «большой» вариант, создав класс LargeBurger. Теперь мы можем определить большой бургер с сыром во время выполнения как:

b = LargeBurger.new(cheese_burger)
b.price  # => 50 + 15 + 20 = 85

Помните, как использование наследования для добавления варианта "с картофелем фри" включало бы добавление еще 4 подклассов? С помощью декораторов мы просто создали бы один новый класс, BurgerWithFries, для обработки нового варианта и обработки его во время выполнения. Каждому новому свойству потребуется просто больше декоратора, чтобы покрыть все перестановки.

PS. Это краткая версия статьи, которую я написал о с использованием шаблона Decorator в Ruby , которую вы можете прочитать, если хотите найти более подробные примеры.

...