Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Order of decorations in Decorator Pattern

Most of you know the pizza / cofee example for the decorator pattern.

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

the two object behave in a similar way, but not completely, in particular if you have non-commutative operation, for example:

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

So the price for pizza1 and pizza2 are the same, but the name is not, for example the first should be "Big mushroom pizza", the second "Mushroom big pizza". The first is english correct (probably better would be "Big pizza with mushroom", but it's not so important).

The book "Head first" point out this problem with the Cofee example:

When you need to peek at multiple layers into the decorator chain, you are starting to push the decorator beyond its true intent.

Nevertheless, such things are possible. Imagine a CondimentPrettyPrint decorator that parses the final decription and can print “Mocha, Whip, Mocha” as “Whip, Double Mocha.”

what is the best way to do that? (operator< ?)

like image 449
Ruggero Turra Avatar asked Jul 23 '11 11:07

Ruggero Turra


Video Answer


2 Answers

Ive never known this sort of thing to be needed when using decorators. And I would think that if you need to do this, then you shouldn't be using decorators, especially as you're knowingly "pushing the decorator beyond it's intent".

I have had a stab at doing this, the code is below. Basically, I create a thin layer around the SimplePizza object that understands what the decorators need, then the decorators decorate that.

The main problem here, is that to maintain order in the output, you would have to maintain a relationship between decorators - which can quickly become a maintenance nightmare.

#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;
}
like image 122
Node Avatar answered Oct 19 '22 18:10

Node


In terms of where to put such code, have an overloaded operator<< is feasible.

I feel that the "pushing the decorator beyond it's intent" really needs emphasis here.

Would you really build a serious application whose functioning depends on parsing

"Mocha, Whip, Mocha"

and formulating

"Whip, Double Mocha"

Conceptually you are inferring semantics from an interface that is not published with that intent. The result will be very brittle, minor changes in implementations of decorators: "Yummy super mocha special" would break the parser. Adding new decorators would require unknown levels of change.

like image 3
djna Avatar answered Oct 19 '22 19:10

djna