2011-07-23 6 views
6

La mayoría conoce el ejemplo de pizza/café para el patrón de decorador.Orden de decoraciones en Decorator Pattern

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

los dos se comportan objeto de una manera similar, pero no del todo, especialmente si usted tiene la operación no conmutativa, por ejemplo:

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

Por lo tanto el precio de pizza1 y pizza2 son los mismos , pero el nombre no es, por ejemplo, el primero debe ser "Big mushroom pizza", el segundo "Mushroom big pizza". El primero es correcto en inglés (probablemente sea mejor "Big pizza with mushroom", pero no es tan importante).

El libro "de cabeza" señalar este problema con el ejemplo Cofee:

Cuando tenga que mirar en múltiples capas en la cadena de decorador, que están empezando a empujar el decorador más allá de su verdadera intención .

Sin embargo, tales cosas son posibles. Imagine un CondimentPrettyPrint decorador que analiza la decription final y puede imprimir “Mocha, Whip, Mocha” como “Whip, Doble Mocha.”

cuál es la mejor manera de hacerlo? (operator<?)

+0

no importa cuál, el punto es que quiero la misma salida para 'pizza1.name()' y 'pizza2.name()'. –

Respuesta

5

Nunca he sabido este tipo de cosas que se necesitan al usar decoradores. Y creo que si necesitas hacer esto, entonces no deberías usar decoradores, especialmente porque deliberadamente "empujas al decorador más allá de su intención".

He tenido una puñalada al hacer esto, el código está a continuación. Básicamente, creo una capa delgada alrededor del objeto SimplePizza que entiende lo que necesitan los decoradores, y los decoradores lo decoran.

El principal problema aquí, es que para mantener el orden en la salida, debe mantener una relación entre los decoradores, lo que puede convertirse rápidamente en una pesadilla de mantenimiento.

#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

En términos de dónde colocar dicho código, tener un operador sobrecargado < < es factible.

Siento que el "empujar al decorador más allá de su intención" realmente necesita énfasis aquí.

¿de verdad construir una aplicación seria cuyo funcionamiento depende de analizar

"Mocha, Whip, Mocha" 

y formular

"Whip, Double Mocha" 

semántica Conceptualmente está inferir de una interfaz que no se publica con esa intención. El resultado será muy frágil, pequeños cambios en las implementaciones de los decoradores: "Yummy super mocha special" rompería el analizador sintáctico. Agregar nuevos decoradores requeriría niveles desconocidos de cambio.

+0

+1, buen análisis. – iammilind

+0

¿cómo puedo implementarlo?Estoy pensando en otra solución: defina una enumeración como 'enum DecoratorEnum {MUSHROOM = 1, BIG = 2, ...}', pase un vector de 'DecoratorEnum' a una fábrica que primero los clasifique y luego devuelva su objeto decorado, por lo que 'MushroomDecorator' se aplica primero. Por ejemplo, 'Pizza * pizza = DecorateObject (SimplePizza, {BIG, MUSHROOM})' ¿Bueno? –

+1

mi sospecha es que si tienes decoradores no conmutativos, probablemente también tengas decoradores que no puedan recombinarse arbitrariamente. Así que (A, B, C = bueno, B, A, C = malo, pero ¿qué tal A, C? O B, D). Sospecho que debe soltar el patrón de decorador para la pieza de impresión. – djna