Hay una cosa en C ++ que me ha estado haciendo sentir incómoda durante bastante tiempo, porque honestamente no sé cómo hacerlo, aunque parezca simple:
¿Cómo implemento Factory Method en C ++ correctamente?
Objetivo: hacer posible que el cliente pueda crear instancias de algún objeto utilizando métodos de fábrica en lugar de los constructores del objeto, sin consecuencias inaceptables y un impacto en el rendimiento.
Por "patrón de método de fábrica", me refiero a ambos métodos de fábrica estáticos dentro de un objeto o métodos definidos en otra clase, o funciones globales. En general, "el concepto de redirigir la forma normal de creación de instancias de la clase X a cualquier otro lugar que no sea el constructor".
Permítanme leer algunas posibles respuestas en las que he pensado.
0) No hagas fábricas, haz constructores.
Esto suena bien (y de hecho a menudo es la mejor solución), pero no es un remedio general. En primer lugar, hay casos en que la construcción de objetos es una tarea lo suficientemente compleja como para justificar su extracción a otra clase. Pero incluso dejando de lado ese hecho, incluso para objetos simples que usan solo constructores a menudo no funcionan.
El ejemplo más simple que conozco es una clase de vectores 2D. Tan simple, pero complicado. Quiero poder construirlo tanto desde coordenadas cartesianas como polares. Obviamente, no puedo hacer:
struct Vec2 {
Vec2(float x, float y);
Vec2(float angle, float magnitude); // not a valid overload!
// ...
};
Mi forma natural de pensar es entonces:
struct Vec2 {
static Vec2 fromLinear(float x, float y);
static Vec2 fromPolar(float angle, float magnitude);
// ...
};
Lo que, en lugar de los constructores, me lleva al uso de métodos estáticos de fábrica ... lo que esencialmente significa que estoy implementando el patrón de fábrica, de alguna manera ("la clase se convierte en su propia fábrica"). Esto se ve bien (y sería adecuado para este caso en particular), pero falla en algunos casos, que voy a describir en el punto 2. Siga leyendo.
otro caso: tratar de sobrecargar por dos tipos de letra opacos de alguna API (como GUID de dominios no relacionados, o un GUID y un campo de bits), tipos semánticamente totalmente diferentes (por lo tanto, en teoría, sobrecargas válidas) pero que en realidad resultan ser lo mismo, como entradas sin signo o punteros nulos.
1) La forma de Java
Java lo tiene simple, ya que solo tenemos objetos asignados dinámicamente. Hacer una fábrica es tan trivial como:
class FooFactory {
public Foo createFooInSomeWay() {
// can be a static method as well,
// if we don't need the factory to provide its own object semantics
// and just serve as a group of methods
return new Foo(some, args);
}
}
En C ++, esto se traduce en:
class FooFactory {
public:
Foo* createFooInSomeWay() {
return new Foo(some, args);
}
};
¿Frio? A menudo, de hecho. Pero entonces, esto obliga al usuario a usar solo la asignación dinámica. La asignación estática es lo que hace que C ++ sea complejo, pero también es lo que a menudo lo hace poderoso. Además, creo que existen algunos objetivos (palabra clave: incrustado) que no permiten la asignación dinámica. Y eso no implica que a los usuarios de esas plataformas les guste escribir OOP limpio.
De todos modos, dejando de lado la filosofía: en el caso general, no quiero obligar a los usuarios de la fábrica a restringir la asignación dinámica.
2) Retorno por valor
OK, entonces sabemos que 1) es genial cuando queremos una asignación dinámica. ¿Por qué no agregaremos asignación estática además de eso?
class FooFactory {
public:
Foo* createFooInSomeWay() {
return new Foo(some, args);
}
Foo createFooInSomeWay() {
return Foo(some, args);
}
};
¿Qué? ¿No podemos sobrecargar por el tipo de retorno? Oh, por supuesto que no podemos. Así que cambiemos los nombres de los métodos para reflejar eso. Y sí, he escrito el ejemplo de código no válido anterior solo para enfatizar cuánto me disgusta la necesidad de cambiar el nombre del método, por ejemplo, porque ahora no podemos implementar un diseño de fábrica independiente del idioma, ya que tenemos que cambiar los nombres, y Todos los usuarios de este código deberán recordar esa diferencia de la implementación de la especificación.
class FooFactory {
public:
Foo* createDynamicFooInSomeWay() {
return new Foo(some, args);
}
Foo createFooObjectInSomeWay() {
return Foo(some, args);
}
};
OK ... ahí lo tenemos. Es feo, ya que necesitamos cambiar el nombre del método. Es imperfecto, ya que necesitamos escribir el mismo código dos veces. Pero una vez hecho, funciona. ¿Correcto?
Bueno, por lo general. Pero a veces no. Al crear Foo, en realidad dependemos del compilador para hacer la optimización del valor de retorno para nosotros, porque el estándar C ++ es lo suficientemente benevolente para que los proveedores del compilador no especifiquen cuándo se creará el objeto en el lugar y cuándo se copiará al devolver un objeto temporal por valor en C ++. Entonces, si Foo es costoso de copiar, este enfoque es arriesgado.
¿Y si Foo no es copiable en absoluto? Bueno doh. ( Tenga en cuenta que en C ++ 17 con elisión de copia garantizada, no ser copiable ya no es un problema para el código anterior )
Conclusión: Hacer una fábrica devolviendo un objeto es de hecho una solución para algunos casos (como el vector 2-D mencionado anteriormente), pero aún no es un reemplazo general para los constructores.
3) construcción de dos fases
Otra cosa que probablemente se le ocurrirá a alguien es separar el tema de la asignación de objetos y su inicialización. Esto generalmente da como resultado un código como este:
class Foo {
public:
Foo() {
// empty or almost empty
}
// ...
};
class FooFactory {
public:
void createFooInSomeWay(Foo& foo, some, args);
};
void clientCode() {
Foo staticFoo;
auto_ptr<Foo> dynamicFoo = new Foo();
FooFactory factory;
factory.createFooInSomeWay(&staticFoo);
factory.createFooInSomeWay(&dynamicFoo.get());
// ...
}
Uno puede pensar que funciona como un encanto. El único precio que pagamos en nuestro código ...
Como he escrito todo esto y lo he dejado como el último, tampoco me gusta. :) ¿Por qué?
En primer lugar ... me disgusta sinceramente el concepto de construcción en dos fases y me siento culpable cuando lo uso. Si diseño mis objetos con la afirmación de que "si existe, está en estado válido", siento que mi código es más seguro y menos propenso a errores. Me gusta de esa forma.
Tener que abandonar esa convención Y cambiar el diseño de mi objeto solo con el propósito de fabricarlo es ... bueno, difícil de manejar.
Sé que lo anterior no convencerá a muchas personas, así que permítanme dar algunos argumentos más sólidos. Usando la construcción de dos fases, no puede:
- inicializar
const
o referenciar las variables miembro, - pasar argumentos a constructores de clase base y constructores de objetos miembros.
Y probablemente podría haber algunos inconvenientes más en los que no puedo pensar en este momento, y ni siquiera me siento particularmente obligado ya que los puntos anteriores ya me convencen.
Entonces: ni siquiera cerca de una buena solución general para implementar una fábrica.
Conclusiones:
Queremos tener una forma de instanciación de objetos que:
- permitir una instanciación uniforme independientemente de la asignación,
- dar nombres diferentes y significativos a los métodos de construcción (por lo tanto, no depender de la sobrecarga de argumentos),
- no introducir un impacto significativo en el rendimiento y, preferiblemente, un impacto significativo en el aumento de código, especialmente en el lado del cliente,
- ser general, como en: posible ser introducido para cualquier clase.
Creo que he demostrado que las formas que he mencionado no cumplen esos requisitos.
¿Alguna pista? Por favor, proporciónenme una solución. No quiero pensar que este lenguaje no me permita implementar adecuadamente un concepto tan trivial.
delete
. Este tipo de métodos están perfectamente bien, siempre y cuando esté "documentado" (el código fuente es documentación ;-)) que la persona que llama toma posesión del puntero (léase: es responsable de eliminarlo cuando sea apropiado).unique_ptr<T>
lugar deT*
.Respuestas:
Creo que este punto es incorrecto. La complejidad realmente no importa. La relevancia es lo que hace. Si un objeto se puede construir en un solo paso (no como en el patrón de construcción), el constructor es el lugar correcto para hacerlo. Si realmente necesita otra clase para realizar el trabajo, entonces debería ser una clase auxiliar que se utiliza desde el constructor de todos modos.
Hay una solución fácil para esto:
La única desventaja es que se ve un poco detallado:
Pero lo bueno es que puede ver de inmediato qué tipo de coordenadas está utilizando y, al mismo tiempo, no tiene que preocuparse por copiar. Si desea copiar, y es costoso (como lo demuestra el perfil, por supuesto), es posible que desee usar algo como las clases compartidas de Qt para evitar la sobrecarga de la copia.
En cuanto al tipo de asignación, la razón principal para usar el patrón de fábrica suele ser el polimorfismo. Los constructores no pueden ser virtuales, e incluso si pudieran, no tendría mucho sentido. Al usar la asignación estática o de pila, no puede crear objetos de forma polimórfica porque el compilador necesita saber el tamaño exacto. Por lo tanto, solo funciona con punteros y referencias. Y devolver una referencia de una fábrica no funciona también, porque si bien es un objeto técnicamente, se puede eliminar por referencia, podría ser bastante confuso y propenso a errores, consulte ¿Es mal la práctica de devolver una variable de referencia de C ++?por ejemplo. Entonces, los punteros son lo único que queda, y eso también incluye punteros inteligentes. En otras palabras, las fábricas son más útiles cuando se usan con asignación dinámica, por lo que puede hacer cosas como esta:
En otros casos, las fábricas solo ayudan a resolver problemas menores como aquellos con sobrecargas que usted mencionó. Sería bueno si fuera posible usarlos de manera uniforme, pero no duele mucho que probablemente sea imposible.
fuente
Ejemplo simple de fábrica:
fuente
unique_ptr
en ese ejemplo no tiene sobrecarga de rendimiento. Administrar recursos, incluida la memoria, es una de las ventajas supremas de C ++ sobre cualquier otro lenguaje porque puede hacerlo sin penalización de rendimiento y de manera determinista, sin perder el control, pero usted dice exactamente lo contrario. A algunas personas no les gustan las cosas que C ++ hace implícitamente, como la administración de memoria a través de punteros inteligentes, pero si lo que quieres es que todo sea obligatoriamente explícito, usa C; la compensación es de orden de magnitud menos problemas. Creo que es injusto que rechaces una buena recomendación.boost::ptr_vector<>
es un poco más eficiente ya que entiende que posee el puntero en lugar de delegar el trabajo a una subclase. PERO la principal ventajaboost::ptr_vector<>
es que expone a sus miembros por referencia (no puntero), por lo que es realmente fácil de usar con algoritmos en la biblioteca estándar.¿Has pensado en no usar una fábrica en absoluto y, en cambio, hacer un buen uso del sistema de tipos? Puedo pensar en dos enfoques diferentes que hacen este tipo de cosas:
Opción 1:
Lo que te permite escribir cosas como:
Opcion 2:
puede usar "etiquetas" como lo hace el STL con iteradores y demás. Por ejemplo:
Este segundo enfoque le permite escribir código que se ve así:
que también es agradable y expresivo al tiempo que te permite tener prototipos únicos para cada constructor.
fuente
Puedes leer una muy buena solución en: http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus
La mejor solución está en los "comentarios y discusiones", consulte "No es necesario utilizar métodos de creación estáticos".
A partir de esta idea, he hecho una fábrica. Tenga en cuenta que estoy usando Qt, pero puede cambiar QMap y QString para equivalentes estándar.
Uso de la muestra:
fuente
Principalmente estoy de acuerdo con la respuesta aceptada, pero hay una opción de C ++ 11 que no se ha cubierto en las respuestas existentes:
Ejemplo:
Entonces puedes construir objetos en la pila:
Como subobjetos de otras cosas:
O asignado dinámicamente:
¿Cuándo podría usar esto?
Si, en un constructor público, no es posible dar inicializadores significativos para todos los miembros de la clase sin algún cálculo preliminar, entonces podría convertir ese constructor a un método estático. El método estático realiza los cálculos preliminares, luego devuelve un resultado de valor a través de un constructor privado que solo realiza una inicialización a nivel de miembro.
Digo ' podría ' porque depende de qué enfoque proporcione el código más claro sin ser innecesariamente ineficiente.
fuente
Loki tiene un método de fábrica y una fábrica abstracta . Ambos están documentados (ampliamente) en Modern C ++ Design , por Andei Alexandrescu. El método de fábrica probablemente esté más cerca de lo que parece ser, aunque sigue siendo un poco diferente (al menos si la memoria sirve, requiere que registre un tipo antes de que la fábrica pueda crear objetos de ese tipo).
fuente
Function
y las manipulaciones de tipo pueden reemplazarse constd::function
y,<type_traits>
mientras que las referencias lambda, threading y rvalue tienen implicaciones que pueden requerir algunos ajustes menores, no hay un reemplazo estándar para los singletons de fábricas tal como los describe.No trato de responder todas mis preguntas, ya que creo que es demasiado amplio. Solo un par de notas:
Esa clase es, de hecho, un constructor , en lugar de una fábrica.
Entonces podría hacer que su fábrica lo encapsule en un puntero inteligente. Creo que de esta manera puedes tener tu pastel y comértelo también.
Esto también elimina los problemas relacionados con el retorno por valor.
En efecto. Todos los patrones de diseño tienen sus limitaciones y desventajas (específicas del idioma). Se recomienda usarlos solo cuando lo ayuden a resolver su problema, no por su propio bien.
Si buscas la implementación de fábrica "perfecta", buena suerte.
fuente
Esta es mi solución de estilo c ++ 11. El parámetro 'base' es para la clase base de todas las subclases. los creadores, son objetos std :: function para crear instancias de subclase, podrían ser un enlace a su subclase 'función miembro estática' create (algunos argumentos) '. Esto tal vez no sea perfecto, pero funciona para mí. Y es una solución algo "general".
Un ejemplo de uso.
fuente
Patrón de fábrica
Y si su compilador no admite la Optimización del valor de retorno, desactívela, probablemente no contenga mucha optimización en absoluto ...
fuente
Factory
es que es bastante genérico y cubre mucho terreno; una fábrica puede agregar argumentos (dependiendo del entorno / configuración) o proporcionar algo de almacenamiento en caché (relacionado con Flyweight / Pools), por ejemplo, pero estos casos solo tienen sentido en algunas situaciones.Sé que esta pregunta ha sido respondida hace 3 años, pero esto puede ser lo que estabas buscando.
Google lanzó hace un par de semanas una biblioteca que permite asignaciones de objetos dinámicos fáciles y flexibles. Aquí está: http://google-opensource.blogspot.fr/2014/01/introducing-infact-library.html
fuente