Pregunta

Tengo el siguiente código para " factory " implementación del patrón de diseño.

class Pen{
public:
     virtual void Draw() = 0;
};

class RedPen : public Pen{
public:
     virtual void Draw(){
         cout << "Drawing with red pen" << endl;
     }
};

class BluePen : public Pen{
public:
     virtual void Draw(){
         cout << "Drawing with blue pen" << endl;
     }
};

auto_ptr<Pen> createPen(const std::string color){
     if(color == "red")
         return auto_ptr<Pen>(new RedPen);
     else if(color == "blue")
         return auto_ptr<Pen>(new BluePen);
}

Pero escuché que se puede hacer de una mejor manera usando las plantillas "C ++". ¿Alguien puede ayudar cómo se hace y cómo el enfoque de plantilla es mejor que esto?

Cualquier pensamiento

¿Fue útil?

Solución

En el ejemplo que publicó, ni el enfoque de fábrica ni el de plantilla tienen sentido para mí. Mi solución involucra a un miembro de datos en la clase Pen.

class Pen {
public:
    Pen() : m_color(0,0,0,0) /* the default colour is black */
    {            
    }

    Pen(const Color& c) : m_color(c)
    {
    }

    Pen(const Pen& other) : m_color(other.color())
    {
    }

    virtual void Draw()
    {
        cout << "Drawing with a pen of color " << m_color.hex();
    }
    void setColor(const Color& c) { m_color = c; }
    const Color& color() const { return m_color; }
private:
    Color m_color;
};

class Color {
public:
    Color(int r, int g, int b, int a = 0) :
        m_red(r), m_green(g), m_blue(other.blue()), m_alpha(a)  
    {
    }

    Color(const Color& other) : 
        m_red(other.red()), m_green(other.green()), 
        m_blue(other.blue()), m_alpha(other.alpha())
    {
    }

    int red() const { return m_red; }
    int green() const  { return m_green; }
    int blue() const { return m_blue; }
    int alpha() const { return m_alpha; }

    std::string hex() const
    {
        std::ostringstream os;
        char buf[3];
        os << "#";

        sprintf(buf, "%2X", red());
        os << buf;

        sprintf(buf, "%2X", green());
        os << buf;

        sprintf(buf, "%2X", blue());
        os << buf;

        sprintf(buf, "%2X", alpha());
        os << buf;

        return os.str();
    }

private:
    int m_red;
    int m_green;
    int m_blue;
    int m_alpha;
}

Por supuesto, la clase de color tendría que ajustarse a la API de dibujo que usa, y quizás sea mucho más avanzada que esta (diferentes espacios de color, etc.).

¿Por qué no plantillas?

La razón por la que no tiene sentido usar plantillas es que (presumiblemente) la única diferencia entre las diferentes operaciones de dibujo es la variable de color. Entonces, al usar plantillas (o declarar manualmente diferentes clases, como lo hizo), duplicará un código similar. Esto ampliará su programa y lo ralentizará.

Por lo tanto, la función de dibujo debe tomar el color como argumento o (como en mi ejemplo) tener el color como un miembro de datos de clase.

Otros consejos

Otra forma es registrar dinámicamente una función creador en un objeto Factory dinámico.

BluePen *create_BluePen() { return new BluePen; }
static bool BluePen_creator_registered = 
                       Factory::instance()->registerCreator("BluePen", 
                                                            create_BluePen);

Un efecto interesante al hacer esto es que la variable bool estática BluePen-creator-created se configurará antes de que main () comience, haciendo que el registro sea automático.

Estas líneas a veces se hacen a través de macros comunes, es decir, como

#define METAIMPL( _name ) \
_name *create_ ## _name() { return new _name; } \
static bool _name ## _creator_registered = \
                        Factory::instance()->registerCreator(# _name, \
                                                             create_ ## _name)

... y se usa cerca del constructor

METAIMPL( BluePen ); // auto registers to the Factory

BluePen::BluePen() : Pen() {
   // something
}

Entonces la tarea de Factory será almacenar y buscar estas funciones creator . Dejo el resto como el ejercicio ;) es decir, el uso de una macro METADECL

Si desea más información, consulte aquí en el capítulo 4.1 Meta Información que también incluye un método para expandir para incluir posibilidades de inspector características

Aprendí esto usando ET ++ que era un proyecto para portar MacApp antiguo a C ++ y X11. En su esfuerzo, Eric Gamma, etc., comenzó a pensar en Patrones de diseño

Y ... (7 de mayo de 2011) Finalmente apareció un ejemplo para github
https : //github.com/epatel/cpp-factory

Tu fábrica está bien. Lo tomo como BluePen y así sucesivamente eran solo ejemplos de nombres de clase. Puede usar plantillas, si se cumple la siguiente condición:

  

Cuando sepa en tiempo de compilación (es decir, cuando escriba código) que desea que se devuelva un tipo específico, utilice una plantilla. De lo contrario, no puedes.

Eso significa en código que puedes hacer esto:

template<typename PenType>
auto_ptr<Pen> createPen(){
    return auto_ptr<Pen>(new PenType);
}

Teniendo eso en su lugar, puede usar eso como

...
auto_ptr<Pen> p = createPen<BluePen>();
...

Pero ese argumento de plantilla, el BluePen , no puede ser una variable establecida en un tipo en tiempo de ejecución. En su ejemplo, pasa una cadena, que por supuesto se puede establecer en tiempo de ejecución. Entonces, cuando lees que puedes usar plantillas de C ++, entonces esa recomendación solo es condicionalmente cierta; luego, cuando tu decisión, sobre qué lápiz crear, ya está hecha en tiempo de compilación. Si esa condición se ajusta, entonces la solución de la plantilla es lo correcto. No le costará nada en tiempo de ejecución y será exactamente lo que necesita.

Al declarar clases vacías especiales para colores, puede hacer todo con plantillas. Esto requiere que cada opción de color esté disponible en tiempo de compilación. Al hacer esto, evita tener que usar una clase base con métodos virtuales.

struct Red{};
struct Blue{};

template < typename Color >
class Pen{};

template <>
class Pen< Red >
{
     void Draw(){
         cout << "Drawing with red pen" << endl;
     }
};

template <>
class Pen< Blue >
{
     void Draw(){
         cout << "Drawing with blue pen" << endl;
     }
};

template < typename Color >
std::auto_ptr< Pen< Color > > createPen()
{
     return auto_ptr< Pen< Color > >(new Pen< Color >());
}

Podría escribir una clase de fábrica de objetos genéricos como una clase de plantilla (o usar la que se describe muy bien en este artículo de gamedev.net ).

De esta manera, si tiene más de una fábrica en su código, es menos trabajo definir cada fábrica.

Como complemento a mi otra respuesta, solo para discutir el patrón de Fábrica vs. el uso de la plantilla:

La razón principal (y más simple) para usar plantillas es que su código es idéntico en todos los casos, excepto por los tipos de datos en los que trabaja. Ejemplos aquí son los contenedores STL. Sería posible escribir una función de fábrica createVector (" string ") y escribir cada contenedor manualmente, pero esto es claramente subóptimo.

Incluso cuando el código difiere, no solo los tipos de datos, es posible usar especializaciones de plantilla, pero en muchos casos, una función de fábrica tendría más sentido.

Como ejemplo, considere una biblioteca de abstracción de base de datos. Sería posible usar especializaciones de plantilla, de modo que la biblioteca se pudiera usar como " db :: driver " ;. Pero esto lo obligaría a escribir el tipo de base de datos en todas partes del código (haciendo que la biblioteca sea un poco inútil en primer lugar ...), o realizar un caso en una interfaz tipo db :: driver class.

En este ejemplo, es más intuitivo decir db :: get_driver (odbc) y recuperar el reparto de clase adecuado para el tipo de interfaz.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top