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.
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]
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:
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.
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]
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 DocumentCreator
que 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 DocumentCreator
clase, como FancyDocumentCreator
o ModernDocumentCreator
, cada una con una implementación diferente de createLetter()
y createResume()
que crearía objetos correspondientes, como FancyLetter
o ModernResume
. Cada uno de estos productos se deriva de una clase abstracta simple , como Letter
o , Resume
de la que el cliente es consciente. El código del cliente adquiriría una instancia apropiada de DocumentCreator
y llamaría a sus métodos de fábrica . Cada uno de los objetos resultantes se crearía a partir de la misma DocumentCreator
implementación y compartiría un tema común. El cliente solo necesitaría saber cómo manejar la clase abstracta Letter
o 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:
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 .Client
ProductA
ProductB
ProductA1
ProductB1
Client
AbstractFactory
Client
Factory1
AbstractFactory
ProductA1
ProductB1
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.Client
createProductA()
Factory1
ProductA1
Client
createProductB()
Factory1
ProductB1
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]
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 , fábrica . makeWall ()); r2 -> setSide ( Oeste , una Puerta ); devolver un laberinto ; } }; 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::setLado 2 0x13183c0 Habitación::setLado 1 0x13183e0 Habitación::setLado 3 0x1317f10
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 )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 )