pattern - Implementación de método de fábrica-C++
factory pattern c# (6)
Tengo el siguiente código para la implementación del patrón de diseño "de fábrica".
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 manera mejor usando "plantillas C ++". ¿Alguien puede ayudarme a cómo se hace y cómo el enfoque de la plantilla es mejor que esto?
Alguna idea
Como complemento de mi otra respuesta, solo para analizar el patrón de Fábrica vs. uso de plantillas:
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 funciona. Ejemplos aquí son los contenedores STL. Sería posible escribir una función de fábrica createVector ("cadena") y escribir cada contenedor manualmente, pero esto claramente no es óptimo.
Incluso cuando el código difiere, no solo en 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, para que la biblioteca se pueda usar como "db :: driver". Pero esto te obligaría a escribir el tipo de base de datos en todas partes del código (haciendo que la biblioteca sea inútil en primer lugar ...), o realizar un caso a una clase de interfaz tipo db :: driver.
En este ejemplo, es más intuitivo decir db :: get_driver (odbc) y recuperar el molde de clase adecuado para el tipo de interfaz.
En el ejemplo que publicó, ni una fábrica ni un enfoque 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 debería ajustarse a la API de dibujo que utilice, y tal vez sea mucho más avanzada que esta (espacios de color diferentes, etc.).
¿Por qué no plantillas?
La razón por la que no tiene sentido utilizar 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 hará que su programa sea grande y lo desacelerará.
Entonces, la función de dibujar debe tomar el color como argumento o (como en mi ejemplo) tener el color como un miembro de datos de clase.
Tu fábrica está bien. Lo tomo como BluePen
y así sucesivamente fueron 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 escribe código) que quiere que se devuelva un tipo específico, entonces use 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, puedes usar eso como
...
auto_ptr<Pen> p = createPen<BluePen>();
...
Pero ese argumento de plantilla, BluePen
, no puede ser una variable configurada en un tipo en tiempo de ejecución. En su ejemplo, pasa una cadena, que por supuesto puede establecerse en tiempo de ejecución. Entonces, cuando usted lee que puede usar Plantillas C ++, entonces esa recomendación solo es condicionalmente verdadera, entonces, cuando su decisión, de qué bolígrafo crear, ya está hecha en tiempo de compilación. Si esa condición se ajusta, entonces la solución de 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 usando 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 >());
}
Otra forma es registrar dinámicamente una función de creador a 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 estática bool BluePen-creator-registered
se establecerá antes de que main()
comience por lo que el registro se automatizará.
Estas líneas a veces se hacen a través de macros ordinarios, es decir, como
#define METAIMPL( _name ) /
_name *create_ ## _name() { return new _name; } /
static bool _name ## _creator_registered = /
Factory::instance()->registerCreator(# _name, /
create_ ## _name)
... y usado 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 de creador . Dejo el resto como ejercicio ; es decir, el uso de una macro METADECL
Si desea obtener más información, consulte aquí en el capítulo 4.1 Meta información, que también incluye un método de expansión para incluir posibilidades de funciones de inspector.
Aprendí esto del uso de ET ++ que era un proyecto para portar MacApp antiguo a C ++ y X11. En su esfuerzo, Eric Gamma, etc. comenzó a pensar en los patrones de diseño
Y ... (7 de mayo de 2011) Finalmente vino a dar un ejemplo a github
https://github.com/epatel/cpp-factory
Podrías escribir una clase de fábrica de objetos genéricos como clase de plantilla (o utilizar la que está muy bien descrita 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.