装饰模式中的装饰顺序

时间:2011-07-23 11:14:00

标签: c++ design-patterns decorator

大多数人都知道装饰模式的披萨/咖啡示例。

Pizza* pizza1 = BigPizzaDecorator(MushromDecorator(SimplePizza()));
Pizza* pizza2 = MushromDecorator(BigPizzaDecorator(SimplePizza()));

这两个对象的行为方式类似,但不完全,特别是如果你有非交换操作,例如:

BigPizzaDecorator::price() { return 10 + PizzaDecorator::price(); }  // this is commutative
BigPizzaDecorator::name() { return "big " + PizzaDecorator::name(); } // this is not commutative

因此pizza1pizza2的价格相同,但名称不相同,例如第一个应为"Big mushroom pizza",第二个"Mushroom big pizza"。第一个是英语正确(可能更好的是“蘑菇大披萨”,但它并不那么重要)。

“Head first”一书指出了Cofee示例中的这个问题:

  

当你需要在装饰链中窥视多个层时,你   正在开始推动装饰者超越其真正的意图。

     

然而,这样的事情是可能的。想象一下CondimentPrettyPrint   解析最终解密的装饰者可以打印“摩卡,鞭子,   Mocha“as”Whip,Double Mocha。“

最好的方法是什么? (operator<?)

2 个答案:

答案 0 :(得分:5)

我从来不知道在使用装饰器时需要这种东西。而且我认为如果你需要这样做,那么你就不应该使用装饰器,特别是当你故意“推动装饰器超出它的意图”时。

我已经做了这个,代码如下。基本上,我在SimplePizza对象周围创建一个薄层,了解装饰器需要什么,然后装饰器装饰它。

这里的主要问题是,为了维持输出中的顺序,你必须维持装饰器之间的关系 - 这很快就会成为维护的噩梦。

#include <iostream>
#include <queue>
#include <sstream>

struct name_part
{
    std::string mName;
    int         mPriority;

    name_part(const std::string& name, int priority)
    : mName(name)
    , mPriority(priority)
    {
    }
};

bool operator<(const name_part& a, const name_part& b)
{
    return (a.mPriority < b.mPriority);
}

std::string priority_queueToString(const std::priority_queue<name_part>& orig)
{
    std::ostringstream oss;
    std::priority_queue<name_part> q(orig);

    while (!q.empty())
    {
        oss << q.top().mName << " ";
        q.pop();
    }

    return oss.str();
}

struct SimplePizza
{
    virtual std::string name()
    {
        return "pizza";
    }
};

struct SimplePizzaImplementer : SimplePizza
{
    SimplePizza *mDecorated;

    SimplePizzaImplementer()
    : mDecorated(0)
    {
    }

    SimplePizzaImplementer(SimplePizza *decorated)
    : mDecorated(decorated)
    {
    }

    virtual std::string name()
    {
        return priority_queueToString(nameParts());
    }

    virtual std::priority_queue<name_part> nameParts()
    {
        std::priority_queue<name_part> q;

        if (mDecorated)
        {
            q.push(name_part(mDecorated->name(), 0));
        }

        return q;
    }
};

struct MushroomDecorator : SimplePizzaImplementer
{
    SimplePizzaImplementer *mDecorated;

    MushroomDecorator(SimplePizzaImplementer *decorated)
    : mDecorated(decorated)
    {
    }

    virtual std::string name()
    {
        return priority_queueToString(nameParts());
    }

    virtual std::priority_queue<name_part> nameParts()
    {
        std::priority_queue<name_part> q = mDecorated->nameParts();
        q.push(name_part("mushroom", 1));
        return q;
    }
};

struct BigDecorator : SimplePizzaImplementer
{
    SimplePizzaImplementer *mDecorated;

    BigDecorator(SimplePizzaImplementer *decorated)
    : mDecorated(decorated)
    {
    }

    virtual std::string name()
    {
        return priority_queueToString(nameParts());
    }

    virtual std::priority_queue<name_part> nameParts()
    {
        std::priority_queue<name_part> q = mDecorated->nameParts();
        q.push(name_part("big", 2));
        return q;
    }
};

int main()
{
    SimplePizzaImplementer *impl = new SimplePizzaImplementer(new SimplePizza());
    SimplePizza *pizza1 = new MushroomDecorator(new BigDecorator(impl));
    SimplePizza *pizza2 = new BigDecorator(new MushroomDecorator(impl));

    std::cout << pizza1->name() << std::endl;
    std::cout << pizza2->name() << std::endl;
}

答案 1 :(得分:2)

就放置此类代码的位置而言,请重载运算符&lt;&lt;是可行的。

我觉得“推动装饰者超越它的意图”真的需要强调。

你真的会构建一个严重的应用程序,其功能取决于解析

"Mocha, Whip, Mocha"

并制定

"Whip, Double Mocha"

从概念上讲,您是从未使用该意图发布的接口推断语义。结果将非常脆弱,装饰器的实现中的微小变化:“Yummy super mocha special”会打破解析器。添加新装饰器需要未知级别的更改。