装饰器模式中装饰器的顺序

10

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

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的价格相同,但名称不同,例如第一个应该是"大蘑菇披萨",第二个应该是"蘑菇大披萨"。第一个英文正确(可能更好的说法是"带蘑菇的大披萨",但这并不是很重要)。

书籍“Head First”以Cofee示例指出了这个问题:

当您需要查看装饰器链中的多个层次时,您开始将装饰器推向其真正意图之外。

尽管如此,这种事情是可能的。想象一个CondimentPrettyPrint装饰器,可以解析最终描述,并将“Mocha,Whip,Mocha”打印为“Whip,Double Mocha”。

怎么做才是最好的方法? (operator<?)


“pizza1.name()”和“pizza2.name()”的输出结果相同是重要的,而不是它们各自的值。 - Ruggero Turra
2个回答

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

3

关于放置此类代码的位置,有一个重载运算符<<是可行的。

我认为“将装饰器推到超出其意图”的重点需要在这里强调。

你会真的构建一个依赖解析的严肃应用程序吗?

"Mocha, Whip, Mocha"

并制定
"Whip, Double Mocha"

概念上,您正在从未发布意图的接口中推断语义。结果会非常脆弱,装饰器实现中的微小更改:“美味超级摩卡特调”将会破坏解析器。添加新的装饰器将需要未知程度的更改。


我该如何实现它?我在考虑另一个解决方案:定义一个枚举,例如 enum DecoratorEnum {MUSHROOM=1, BIG=2, ...},将 DecoratorEnum 的向量传递给工厂,首先对它们进行排序,然后返回装饰后的对象,因此 MushroomDecorator 首先被应用。例如 Pizza *pizza = DecorateObject(SimplePizza, {BIG, MUSHROOM}) 好吗? - Ruggero Turra
1
我的怀疑是,如果您有非交换装饰器,则可能还有不能任意重新组合的装饰器。因此(A,B,C = 好; B,A,C = 坏;但是A,C如何?或B,D)。我怀疑您需要放弃打印部分的装饰器模式。 - djna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接