
El patrón de fábrica abstracta en ingeniería de software es un patrón de diseño que proporciona una forma de crear familias de objetos relacionados sin imponer sus clases concretas, encapsulando un grupo de fábricas individuales que tienen un tema común sin especificar sus clases concretas. [1] Según este patrón, un componente de software cliente crea una implementación concreta de la fábrica abstracta y luego utiliza la interfaz genérica de la fábrica para crear los objetos concretos que forman parte de la familia. El cliente no sabe qué objetos concretos recibe de cada una de estas fábricas internas, ya que utiliza solo las interfaces genéricas de sus productos. [1] Este patrón separa los detalles de implementación de un conjunto de objetos de su uso general y se basa en la composición de objetos, ya que la creación de objetos se implementa en métodos expuestos en la interfaz de la fábrica. [2]
El uso de este patrón permite implementar implementaciones concretas intercambiables sin cambiar el código que las utiliza, incluso en tiempo de ejecución . Sin embargo, el uso de este patrón, al igual que con patrones de diseño similares , puede generar una complejidad innecesaria y trabajo extra en la escritura inicial del código. Además, niveles más altos de separación y abstracción pueden generar sistemas que sean más difíciles de depurar y mantener.
Descripción general
El patrón de diseño de fábrica abstracta es uno de los 23 patrones descritos en el libro Design Patterns de 1994. Puede utilizarse para resolver problemas como: [3]
- ¿Cómo puede una aplicación ser independiente de cómo se crean sus objetos?
- ¿Cómo puede una clase ser independiente de cómo se crean los objetos que requiere?
- ¿Cómo se pueden crear familias de objetos relacionados o dependientes?
La creación de objetos directamente dentro de la clase que los requiere es inflexible. Al hacerlo, se compromete la clase con objetos particulares y se hace imposible cambiar la instanciación más adelante sin cambiar la clase. Esto impide que la clase sea reutilizable si se requieren otros objetos y dificulta la prueba de la clase porque los objetos reales no se pueden reemplazar con objetos simulados.
Una fábrica es la ubicación de una clase concreta en el código en la que se construyen los objetos . La implementación del patrón pretende aislar la creación de objetos de su uso y crear familias de objetos relacionados sin depender de sus clases concretas. [2] Esto permite introducir nuevos tipos derivados sin cambiar el código que utiliza la clase base .
El patrón describe cómo resolver tales problemas:
- Encapsule la creación de objetos en un objeto separado (de fábrica) definiendo e implementando una interfaz para crear objetos.
- Delegar la creación de objetos a un objeto de fábrica en lugar de crear objetos directamente.
Esto hace que una clase sea independiente de cómo se crean sus objetos. Una clase puede configurarse con un objeto de fábrica, que utiliza para crear objetos, y el objeto de fábrica puede intercambiarse en tiempo de ejecución.
Definición
Design Patterns describe el patrón de fábrica abstracto como "una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas". [4]
Uso
La fábrica determina el tipo concreto del objeto que se va a crear y es aquí donde se crea realmente el objeto. Sin embargo, la fábrica solo devuelve una referencia (en Java, por ejemplo, mediante el operador new ) o un puntero de un tipo abstracto al objeto concreto creado.
Esto aísla el código del cliente de la creación de objetos al hacer que los clientes soliciten que un objeto de fábrica cree un objeto del tipo abstracto deseado y devuelva un puntero abstracto al objeto. [5]
Un ejemplo es una clase de fábrica abstracta DocumentCreatorque proporciona interfaces para crear una serie de productos (por ejemplo, createLetter()y createResume()). El sistema tendría cualquier número de versiones concretas derivadas de la DocumentCreatorclase, como FancyDocumentCreatoro ModernDocumentCreator, cada una con una implementación diferente de createLetter()y createResume()que crearía objetos correspondientes, como FancyLettero ModernResume. Cada uno de estos productos se deriva de una clase abstracta simple , como Lettero , Resumede la que el cliente es consciente. El código del cliente adquiriría una instancia apropiada de DocumentCreatory llamaría a sus métodos de fábrica . Cada uno de los objetos resultantes se crearía a partir de la misma DocumentCreatorimplementación y compartiría un tema común. El cliente solo necesitaría saber cómo manejar la clase abstracta Lettero Resume, no la versión específica que fue creada por la fábrica concreta.
Como la fábrica solo devuelve una referencia o un puntero a un tipo abstracto, el código del cliente que solicitó el objeto a la fábrica no conoce (y no se ve afectado por) el tipo concreto real del objeto que se creó. Sin embargo, la fábrica abstracta conoce el tipo de un objeto concreto (y, por lo tanto, una fábrica concreta). Por ejemplo, la fábrica puede leer el tipo del objeto desde un archivo de configuración. El cliente no tiene necesidad de especificar el tipo, ya que este ya se ha especificado en el archivo de configuración. En particular, esto significa:
- El código del cliente no tiene conocimiento del tipo concreto , por lo que no necesita incluir ningún archivo de encabezado ni declaraciones de clase relacionadas con él. El código del cliente solo se ocupa del tipo abstracto. Los objetos de un tipo concreto son creados por la fábrica, pero el código del cliente accede a dichos objetos solo a través de sus interfaces abstractas . [6]
- La adición de nuevos tipos concretos se realiza modificando el código del cliente para utilizar una fábrica diferente, una modificación que normalmente es una línea en un archivo. La fábrica diferente crea entonces objetos de un tipo concreto diferente pero sigue devolviendo un puntero del mismo tipo abstracto que antes, aislando así el código del cliente de los cambios. Esto es significativamente más fácil que modificar el código del cliente para instanciar un nuevo tipo. Hacerlo requeriría cambiar cada ubicación en el código donde se crea un nuevo objeto, así como asegurar que todas esas ubicaciones de código tengan conocimiento del nuevo tipo concreto, por ejemplo, incluyendo un archivo de encabezado de clase concreto. Si todos los objetos de la fábrica se almacenan globalmente en un objeto singleton , y todo el código del cliente pasa a través del singleton para acceder a la fábrica adecuada para la creación de objetos, entonces cambiar las fábricas es tan fácil como cambiar el objeto singleton. [6]
Estructura
Diagrama UML
![Un ejemplo de diagrama de clase y secuencia UML para el patrón de diseño de fábrica abstracta. [7]](https://images.hispanopedia.wiki/wikipedia/commons/a/aa/W3sDesign_Abstract_Factory_Design_Pattern_UML.jpg)
En el diagrama de clases UML anterior , la clase que requiere objetos y no instancia las clases y directamente. En cambio, hace referencia a la interfaz para crear objetos, lo que hace que la interfaz sea independiente de cómo se crean los objetos (qué clases concretas se instancian). La clase implementa la interfaz instanciando las clases y .
ClientProductAProductBProductA1ProductB1ClientAbstractFactoryClientFactory1AbstractFactoryProductA1ProductB1
El diagrama de secuencia UML muestra las interacciones en tiempo de ejecución. El objeto llama al objeto, que crea y devuelve un objeto. A continuación, llama a , que crea y devuelve un objeto.
ClientcreateProductA()Factory1ProductA1ClientcreateProductB()Factory1ProductB1
Variantes
La estructura original del patrón de fábrica abstracta, tal como se definió en 1994 en Design Patterns , se basa en clases abstractas para la fábrica abstracta y los productos abstractos que se crearán. Las fábricas y los productos concretos son clases que especializan las clases abstractas mediante herencia. [4]
Una estructura más reciente del patrón se basa en interfaces que definen la fábrica abstracta y los productos abstractos que se van a crear. Este diseño utiliza soporte nativo para interfaces o protocolos en los lenguajes de programación más utilizados para evitar la herencia. En este caso, las fábricas y los productos concretos son clases que materializan la interfaz al implementarla. [1]
Ejemplo
Esta implementación de C++11 se basa en la implementación anterior a C++98 del libro.
#include <flujo de datos>
enumeración Dirección { Norte , Sur , Este , Oeste };
clase MapSite { público : virtual void enter () = 0 ; virtual ~ MapSite () = predeterminado ; };
clase Habitación : público MapSite { público : Habitación () : númeroDeHabitación ( 0 ) {} Habitación ( int n ) : númeroDeHabitación ( n ) {} void setSide ( Dirección d , MapSite * ms ) { std :: cout << "Habitación::setSide " << d << ' ' << ms << '\n' ; } virtual void enter () {} Habitación ( const Habitación & ) = delete ; // regla de tres Habitación & operador = ( const Habitación & ) = delete ; privado : int númeroDeHabitación ; };
clase Muro : público MapSite { público : Muro () {} virtual void enter () {} };
clase Puerta : público MapSite { público : Puerta ( Habitación * r1 = nullptr , Habitación * r2 = nullptr ) : habitación1 ( r1 ), habitación2 ( r2 ) {} virtual void enter () {} Puerta ( const Puerta & ) = eliminar ; // regla de tres Puerta & operador = ( const Puerta & ) = eliminar ; privado : Habitación * habitación1 ; Habitación * habitación2 ; };
clase Laberinto { público : void addRoom ( Habitación * r ) { std :: cout << "Laberinto::addRoom " << r << '\n' ; } Habitación * roomNo ( int ) const { return nullptr ; } };
clase MazeFactory { público : MazeFactory () = predeterminado ; virtual ~ MazeFactory () = predeterminado ;
Laberinto virtual * makeMaze () const { devuelve nuevo Laberinto ; } Muro virtual * makeWall () const { devuelve nuevo Muro ; } Habitación virtual * makeRoom ( int n ) const { devuelve nueva Habitación ( n ); } Puerta virtual * makeDoor ( Habitación * r1 , Habitación * r2 ) const { devuelve nueva Puerta ( r1 , r2 ); } };
// Si se pasa un objeto a createMaze como parámetro para crear habitaciones, paredes y puertas, entonces se pueden cambiar las clases de habitaciones, paredes y puertas pasando un parámetro diferente. Este es un ejemplo del patrón Abstract Factory (99).
clase MazeGame { public : Maze * createMaze ( MazeFactory & factory ) { Laberinto * aMaze = factory . makeMaze (); Habitación * r1 = factory . makeRoom ( 1 ); Habitación * r2 = factory . makeRoom ( 2 ); Puerta * aDoor = factory . makeDoor ( r1 , r2 ); aMaze -> addRoom ( r1 ); aMaze -> addRoom ( r2 ); r1 -> setSide ( Norte , factory . makeWall ()); r1 -> setSide ( Este , aDoor ); r1 -> setSide ( Sur , factory . makeWall ()); r1 -> setSide ( Oeste , factory . makeWall ()); r2 -> setSide ( Norte , factory . makeWall ()); r2 -> setSide ( Este , factory . makeWall ()); r2 -> setSide ( Sur , fabrica.makeWall ( ) ); r2 -> setSide ( Oeste , unaPuerta ); return unLaberinto ; } } ;
int main ( ) { MazeGame juego ; MazeFactory fábrica ; juego.createMaze ( fábrica ) ; }
La salida del programa es:
Laberinto::addRoom 0x1317ed0
Laberinto::addRoom 0x1317ef0
Habitación::setSide 0 0x1318340
Habitación::setSide 2 0x1317f10
Habitación::setSide 1 0x1318360
Habitación::setSide 3 0x1318380
Habitación::setSide 0 0x13183a0 Habitación::
setSide 2 0x13183c0 Habitación::setSide 1
0x13183e0
Habitación::setSide 3 0x1317f10
Véase también
Referencias
- ^ abc Freeman, Eric; Robson, Elisabeth; Sierra, Kathy; Bates, Bert (2004). Hendrickson, Mike; Loukides, Mike (eds.). Patrones de diseño Head First (libro de bolsillo) . Vol. 1. O'REILLY. pág. 156. ISBN 978-0-596-00712-6. Recuperado el 12 de septiembre de 2012 .
- ^ ab Freeman, Eric; Robson, Elisabeth; Sierra, Kathy; Bates, Bert (2004). Hendrickson, Mike; Loukides, Mike (eds.). Patrones de diseño Head First (libro de bolsillo) . Vol. 1. O'REILLY. pág. 162. ISBN 978-0-596-00712-6. Recuperado el 12 de septiembre de 2012 .
- ^ "El patrón de diseño Abstract Factory: problema, solución y aplicabilidad". w3sDesign.com . Consultado el 11 de agosto de 2017 .
- ^ ab Gamma, Erich; Richard Helm; Ralph Johnson; John M. Vlissides (23 de octubre de 2009). "Patrones de diseño: Abstract Factory". informIT. Archivado desde el original el 16 de mayo de 2012. Consultado el 16 de mayo de 2012.
Creación de objetos: Abstract Factory: Propósito: Proporcionar una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas.
{{cite web}}: CS1 maint: bot: estado de URL original desconocido ( enlace ) - ^ Veeneman, David (23 de octubre de 2009). "Diseño de objetos para los perplejos". The Code Project. Archivado desde el original el 21 de febrero de 2011. Consultado el 16 de mayo de 2012.
La fábrica aísla al cliente de los cambios en el producto o en la forma en que se crea, y puede proporcionar este aislamiento en objetos derivados de interfaces abstractas muy diferentes.
{{cite web}}: CS1 maint: bot: estado de URL original desconocido ( enlace ) - ^ ab "Abstract Factory: Implementation". OODesign.com . Consultado el 16 de mayo de 2012 .
- ^ "El patrón de diseño Abstract Factory: estructura y colaboración". w3sDesign.com . Consultado el 12 de agosto de 2017 .