【问题标题】:Order of decorations in Decorator Pattern装饰器模式中的装饰顺序
【发布时间】:2011-07-23 11:13:59
【问题描述】:

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

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 的例子指出了这个问题:

当您需要查看装饰器链的多个层时,您 开始将装饰器推到超出其真正意图的范围内。

尽管如此,这样的事情是可能的。想象一下调味品PrettyPrint 解析最终描述并可以打印“Mocha, Whip, 摩卡”作为“鞭子,双摩卡”。

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

【问题讨论】:

  • 哪一个并不重要,关键是我想要pizza1.name()pizza2.name() 的输出相同。

标签: c++ design-patterns decorator


【解决方案1】:

我从来不知道在使用装饰器时需要这种东西。而且我认为,如果您需要这样做,那么您不应该使用装饰器,尤其是当您故意“将装饰器推到其意图之外”时。

我已经尝试过这样做,代码如下。基本上,我在 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;
}

【讨论】:

    【解决方案2】:

    就放置此类代码的位置而言,有一个重载的运算符

    我觉得“将装饰器推到超出其意图的范围内”确实需要在这里强调。

    你真的会构建一个功能依赖于解析的严肃应用程序

    "Mocha, Whip, Mocha"
    

    和制定

    "Whip, Double Mocha"
    

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

    【讨论】:

    • 我该如何实现呢?我正在考虑另一种解决方案:定义一个像enum DecoratorEnum {MUSHROOM=1, BIG=2, ...} 这样的枚举,将DecoratorEnum 的向量传递给首先对它们进行排序的工厂,然后返回装饰过的对象,因此首先应用MushroomDecorator。例如Pizza *pizza = DecorateObject(SimplePizza, {BIG, MUSHROOM}) 好吗?
    • 我的怀疑是,如果你有非交换装饰器,你可能还有不能任意重组的装饰器。所以(A、B、C = 好;B、A、C = 坏;但是 A、C 或 B、D 怎么样?)。我怀疑您需要放弃印刷品的装饰图案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-07
    • 2015-02-05
    • 1970-01-01
    • 2018-04-10
    • 2012-02-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多