Articulo de referencia

Patrón de fábrica abstracto

Diagrama de clases UML 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 im...

Diagrama de clases UML

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 comparten 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 desconoce qué objetos concretos recibe de cada una de estas fábricas internas, ya que solo utiliza 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 intercambiar implementaciones concretas sin modificar el código que las utiliza, incluso en tiempo de ejecución . Sin embargo, su empleo, al igual que el de otros patrones de diseño similares , puede generar complejidad innecesaria y trabajo adicional en la escritura inicial del código. Además, un mayor nivel de separación y abstracción puede dar lugar a sistemas 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?

Crear objetos directamente dentro de la clase que los requiere es inflexible. Al hacerlo, la clase queda vinculada a objetos específicos e imposibilita modificar la instanciación posteriormente sin cambiar la clase. Esto impide que la clase sea reutilizable si se requieren otros objetos y dificulta las pruebas, ya que los objetos reales no pueden reemplazarse con objetos simulados.

Una fábrica es la ubicación de una clase concreta en el código donde se construyen los objetos . La implementación del patrón busca 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 modificar el código que utiliza la clase base .

El patrón describe cómo resolver este tipo de problemas:

  • Encapsular la creación de objetos en un objeto separado (de fábrica) definiendo e implementando una interfaz para la creación de objetos.
  • Delegar la creación de objetos a un objeto de fábrica en lugar de crearlos 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 dicho 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 de objeto que se va a crear, y es aquí donde se crea dicho objeto. Sin embargo, la fábrica solo devuelve una referencia (en Java, por ejemplo, mediante el operador `new` ) o un puntero de 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 varios 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 cual 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 cliente que solicitó el objeto a la fábrica no conoce —ni se ve afectado por— el tipo concreto real del objeto creado. Sin embargo, la fábrica abstracta conoce el tipo de un objeto concreto (y, por lo tanto, de una fábrica concreta). Por ejemplo, la fábrica puede leer el tipo del objeto de un archivo de configuración. El cliente no necesita especificar el tipo, ya que este ya está especificado en el archivo de configuración. En concreto, esto significa:

  • El código del cliente desconoce el tipo concreto , por lo que no necesita incluir archivos de cabecera ni declaraciones de clase relacionadas con él. El código del cliente solo trabaja con el tipo abstracto. Si bien la fábrica crea objetos de un tipo concreto, el código del cliente accede a ellos únicamente a través de sus interfaces abstractas . [ 6 ]
  • Agregar nuevos tipos concretos se realiza modificando el código del cliente para usar una fábrica diferente, una modificación que generalmente consiste en una sola línea en un archivo. La nueva fábrica crea 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. Hacer esto requeriría cambiar cada ubicación en el código donde se crea un nuevo objeto, además de 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 clases y secuencia UML para el patrón de diseño de fábrica abstracta.
Un ejemplo de diagrama de clases y secuencia UML para el patrón de diseño de fábrica abstracta. [ 8 ]

En el diagrama de clases UML anterior , la clase que requiere objetos no instancia las clases directamente. En cambio, se refiere a la interfaz para crear objetos, lo que hace que sea independiente de cómo se crean los objetos (qué clases concretas se instancian). La clase implementa la interfaz instanciando las clases .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. Posteriormente, 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 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 crearán. Este diseño utiliza el soporte nativo para interfaces o protocolos en los lenguajes de programación más comunes para evitar la herencia. En este caso, las fábricas y los productos concretos son clases que implementan la interfaz. [ 1 ]

Ejemplo

Esta implementación en C++23 se basa en la implementación anterior a C++98 que aparece en el libro.

importar std ;using std :: array ; using std :: shared_ptr ; using std :: unique_ptr ; using std :: vector ;clase MapSite { público : enum clase Direction : char { NORTH , SOUTH , EAST , WEST , };virtual void enter () = 0 ; virtual ~ MapSite () = default ; };clase Habitación : público MapSite { privado : int roomNumber ; shared_ptr < array < MapSite , 4 >> sides ; público : explicit Room ( int n = 0 ) : roomNumber { n } {}~ Habitación () = predeterminado ;Habitación & setSide ( MapSite :: Direction d , MapSite & ms ) { sides [ std :: to_underlying ( d )] = std :: move ( ms ); std :: println ( "Habitación::setSide {} ms" , d ); return * this ; }virtual void enter () override = 0 ;Habitación ( const Habitación & ) = eliminar ; Habitación & operador = ( const Habitación & ) = eliminar ; };clase Muro : público MapSite { público : muro explícito ( int n = 0 ) : MapSite ( n ) {}~ Pared () = predeterminado ;void enter () override { // ... } };clase Puerta : público MapSite { privado : shared_ptr < Habitación > habitación1 ; shared_ptr < Habitación > habitación2 ; público : explicit Door ( int n = 0 , shared_ptr < Habitación > r1 = nullptr , shared_ptr < Habitación > r2 = nullptr ) : MapSite ( n ), habitación1 { std :: mover ( r1 )}, habitación2 { std :: mover ( r2 )} {}~ Puerta () = predeterminado ;void enter () override { // ... }Puerta ( const Puerta & ) = eliminar ; Puerta & operador = ( const Puerta & ) = eliminar ; };clase Laberinto { privado : vector < shared_ptr < Room >> habitaciones ; público : Laberinto () = predeterminado ; ~ Laberinto () = predeterminado ;Maze & addRoom ( shared_ptr < Room > r ) { std :: println ( "Maze::addRoom {}" , reinterpret_cast < void *> ( r . get ())); rooms . push_back ( std :: move ( r )); return * this ; } shared_ptr < Room > roomNo ( int n ) const { for ( const Room & r : rooms ) { // lógica de búsqueda real aquí... } return nullptr ; } };clase MazeFactory { public : MazeFactory () = default ;virtual ~ MazeFactory () = predeterminado ;[[ nodiscard ]] unique_ptr < Maze > makeMaze () const { return std :: make_unique < Maze > (); }[[ nodiscard ]] shared_ptr < Wall > makeWall () const { return std :: make_shared < Wall > (); }[[ nodiscard ]] shared_ptr < Room > makeRoom ( int n ) const { return std :: make_shared < Room > ( new Room ( n )); } [[ nodiscard ]] shared_ptr < Door > makeDoor ( shared_ptr < Room > r1 , shared_ptr < Room > r2 ) const { return std :: make_shared < Door > ( std :: move ( r1 ), std :: move ( r2 )); } };// Si a createMaze se le pasa un objeto como parámetro para crear habitaciones, paredes y puertas, entonces puedes 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 () = default ; ~ Maze () = default ;[[ nodiscard ]] unique_ptr < Maze > createMaze ( MazeFactory & factory ) { unique_ptr < Maze > maze = factory . makeMaze (); shared_ptr < Room > r1 = factory . makeRoom ( 1 ); shared_ptr < Room > r2 = factory . makeRoom ( 2 ); shared_ptr < Door > door = factory . makeDoor ( r1 , r2 ); maze -> addRoom ( r1 ) . addRoom ( r2 ) . setSide ( MapSite :: Direction :: NORTH , factory . makeWall ()) . setSide ( MapSite :: Direction :: EAST , door ) . setSide ( MapSite :: Direction :: SOUTH , factory . makeWall ()) . setSide ( MapSite :: Direction :: WEST , factory . makeWall ()) . setSide ( MapSite :: Dirección :: NORTE , fábrica . makeWall ()) . setSide ( MapSite :: Dirección :: ESTE , fábrica . makeWall ()) . setSide ( MapSite :: Dirección :: SUR , fábrica . makeWall ()) . setSide ( MapSite :: Dirección :: OESTE , puerta ); laberinto de regreso ; } };int main ( int argc , char * argv [ ]) { MazeGame game ; unique_ptr <Maze> maze = game.createMaze ( MazeFactory ( ) ) ; }

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

Véase también

Referencias

  1. 1 2 3 Freeman, Eric; Robson, Elisabeth; Sierra, Kathy; Bates, Bert (2004). Hendrickson, Mike; Loukides, Mike (eds.). Head First Design Patterns (edición de bolsillo) . Vol.  1. O'REILLY. pág.  156. ISBN 978-0-596-00712-6. Consultado el 12 de septiembre de 2012 .
  2. 1 2 Freeman, Eric; Robson, Elisabeth; Sierra, Kathy; Bates, Bert (2004). Hendrickson, Mike; Loukides, Mike (eds.). Head First Design Patterns (edición de bolsillo) . Vol. 1. O'REILLY. pág. 162. ISBN   978-0-596-00712-6. Consultado el 12 de septiembre de 2012 .
  3. "El patrón de diseño Abstract Factory: problema, solución y aplicabilidad" . w3sDesign.com . Consultado el 11 de agosto de 2017 .
  4. 1 2 Gamma, Erich; Richard Helm; Ralph Johnson; John M. Vlissides (23-10-2009). "Patrones de diseño: Fábrica abstracta" . informIT. Archivado del original el 16-05-2012 . Recuperado el 16-05-2012 . Creación de objetos: Fábrica abstracta: Intención: Proporcionar una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas.{{cite web}}: CS1 maint: bot: estado de la URL original desconocido ( enlace )
  5. Veeneman, David (23 de octubre de 2009). "Diseño de objetos para perplejos" . The Code Project. Archivado del original el 21 de febrero de 2011. Recuperado el 16 de mayo de 2012. La fábrica aísla al cliente de los cambios en el producto o en cómo se crea, y puede proporcionar este aislamiento a través de objetos derivados de interfaces abstractas muy diferentes.{{cite web}}: CS1 maint: bot: estado de la URL original desconocido ( enlace )
  6. 1 2 "Fábrica abstracta: Implementación" . OODesign.com . Consultado el 16 de mayo de 2012 .
  7. "El patrón de diseño Abstract Factory: estructura y colaboración" . w3sDesign.com . Consultado el 12 de agosto de 2017 .
  8. "El patrón de diseño Abstract Factory: estructura y colaboración" . w3sDesign.com . Consultado el 12 de agosto de 2017 .
  • Logotipo de Wikimedia CommonsContenido multimedia relacionado con Abstract Factory en Wikimedia Commons
  • Ejemplo de implementación de Abstract Factory