用一个真实的例子来理解"装饰模式"

odi*_*seh 163 design-patterns decorator

我正在研究GOF中记载的装饰模式.

请帮我理解装饰模式.有人可以给出一个用例,说明这在现实世界中有用吗?

thi*_*eek 222

装饰器模式实现了向任何对象动态添加职责的单一目标.

考虑一个披萨店的情况.在披萨店,他们将出售少量披萨品种,他们还将在菜单中提供配料.现在设想一种情况,其中如果披萨店必须为披萨和馅料的每种组合提供价格.即使有四种基本的比萨饼和8种不同的浇头,应用程序也会疯狂地保持比萨饼和浇头的所有这些具体组合.

这是装饰图案.

根据装饰器模式,您将实现浇头作为装饰器和比萨将由那些浇头装饰器装饰.实际上,每个顾客都想要满足他的愿望和最终的账单金额将由基础比萨饼和额外的订购配料组成.每个顶级装饰者都会知道它正在装饰的比萨饼和它的价格.Topping对象的GetPrice()方法将返回披萨和顶部的累积价格.

编辑

这是上面解释的代码示例.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 不喜欢这种模式.也许它就是这个例子.我在OOD方面遇到的主要问题是打顶*不是披萨*.询问披萨的价格,它适用于披萨并不适合我.这是一个非常周到和详细的例子,所以我不是故意要打你. (99认同)
  • @TomW我认为问题的一部分是命名.所有"Topping"类都应该被称为"PizzaWith <Topping>".例如,"PizzaWithMushrooms". (36认同)
  • 从另一个角度来看,这甚至不接近"现实世界".在现实世界中,每次需要在菜单中添加新的顶部(或更改价格)时,都不应重新编译.浇口(通常)存储在数据库中,因此上述示例无用. (17认同)
  • ^这.我认为这是在研究这种模式时一直困扰着我的事情.如果我是一家软件公司并写过披萨店软件,我不想每次都要重新编译和转发.我想在后端的表格中添加一行或者可以轻松地处理他们的要求.好吧,@Stelios Adamantidis.我认为模式最大的优势是修改第三方类. (4认同)
  • 在我看来,装饰器最好尽可能平坦地使用.我的意思是尽可能少的"装饰器包装装饰器".所以也许这个例子不是最合适的.但它非常彻底,这很好. (2认同)
  • 这是一个不好的例子,原因是您没有在这里使用Decorator Pattern解决实际问题。“火腿和蘑菇披萨”不是“下面带有(披萨的火腿)蘑菇”。不,这是一种比萨饼,具有以下成分:[火腿,蘑菇]。如果您正在编写一个真实的应用程序,则只会使整个过程变得比所需复杂。我希望看到一个使用这种模式解决真正问题的示例。 (2认同)

Anu*_*rag 32

这是动态向现有对象添加新行为或Decorator模式的简单示例.由于Javascript等动态语言的性质,这种模式成为语言本身的一部分.

// Person object that we will be decorating with logging capability
var person = {
  name: "Foo",
  city: "Bar"
};

// Function that serves as a decorator and dynamically adds the log method to a given object
function MakeLoggable(object) {
  object.log = function(property) {
    console.log(this[property]);
  }
}

// Person is given the dynamic responsibility here
MakeLoggable(person);

// Using the newly added functionality
person.log('name');
Run Code Online (Sandbox Code Playgroud)

  • 我认为装饰模式的概念在这里不适用。事实上,它根本不是一个模式!是的,您正在运行时添加一个新方法。可能在“switch”或简单的“if”中,您可以声称这是向类动态添加行为的一个很好的例子。但是,我们至少需要两个类来定义装饰器和装饰对象这个图案。 (3认同)
  • @Zich我知道我的示例中没有装饰器,但是通过添加充当装饰器的函数可以轻松解决这个问题。但我的例子中有一个装饰对象。该模式是否在任何地方表明您特别需要两个**类**? (3认同)

fra*_*nkc 18

值得注意的是,Java i/o模型基于装饰器模式.将读者放在...之上的读者之上是一个真正真实的装饰者的例子.


dex*_*ous 7

示例 - 场景 - 假设您正在编写加密模块.此加密可以使用DES - 数据加密标准加密明文件.同样,在系统中,您可以将加密设置为AES - 高级加密标准.此外,您可以使用加密组合 - 首先是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;
}
Run Code Online (Sandbox Code Playgroud)

现在,实现这个接口类 -

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;
    }

};
Run Code Online (Sandbox Code Playgroud)

现在,让我们制作一个装饰器抽象类 - 可以扩展为创建任何类型的风格 - 这里的风格是加密类型.此装饰器抽象类与基类相关.因此,装饰器"是一种"接口类.因此,您需要使用继承.

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;
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们制作一个具体的装饰类 - 加密类型 - 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;
    }

};
Run Code Online (Sandbox Code Playgroud)

现在,让我们说装饰器类型是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;
    }

};
Run Code Online (Sandbox Code Playgroud)

让我们创建一个客户端代码来使用这个装饰器类 -

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;
}
Run Code Online (Sandbox Code Playgroud)

您将看到以下结果 -

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
Run Code Online (Sandbox Code Playgroud)

这是UML图 - 它的类表示.如果您想跳过代码并专注于设计方面.

在此输入图像描述

  • 这个例子不是更适合“策略模式”吗? (2认同)
  • 不,对于策略模式,您无法组合加密方法。因此,您必须为每种可能的组合创建一个策略类。 (2认同)

hus*_*yin 6

装饰器模式可帮助您通过与对象的其他类似子类链接来更改或配置对象的功能。

最好的例子是 java.io 包中的 InputStream 和 OutputStream 类

    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.
Run Code Online (Sandbox Code Playgroud)