Patrón de creación: fábrica abstracta

Dec 04 2022
Aquí hay un artículo que detalla un patrón de diseño creacional que nos permite crear una familia de productos de manera flexible y extensible. ¿Qué es? En el último artículo, aprendimos sobre el patrón del método de fábrica.

Aquí hay un artículo que detalla un patrón de diseño creacional que nos permite crear una familia de productos de manera flexible y extensible.

¿Qué es?

En el último artículo, aprendimos sobre el patrón del método de fábrica . Luego, vimos cómo podíamos modelar las variantes del F-16 utilizando el método de fábrica. Pero este artículo cubrirá cómo representar numerosos aviones además del F-16. Por ejemplo, supongamos que un cliente compra un Boeing 747 para que viaje su CEO y desea que su software sea compatible con este nuevo tipo de avión.

El patrón de fábrica abstracto resuelve el problema de crear familias de productos relacionados. Por ejemplo, el F-16 necesita un motor, una cabina y alas. El Boeing-747 requeriría las mismas piezas, pero serían específicas para Boeing. Cualquier avión necesitaría estas tres partes relacionadas; sin embargo, las partes serán específicas del plano. ¿Puedes ver un patrón emerger aquí? Primero, necesitamos un marco para crear las partes asociadas para cada avión.

El patrón de fábrica abstracto es un patrón de diseño que le permite crear familias de objetos relacionados o dependientes sin especificar sus clases concretas.

Diagrama de clase

El diagrama de clases incluye las siguientes entidades:

  • Fábrica abstracta
  • Fábrica de hormigón
  • Producto abstracto
  • Producto de hormigón
  • Cliente

El patrón de fábrica abstracto le permite crear una familia de objetos relacionados sin especificar sus clases concretas. Consideremos un ejemplo. Supongamos que está creando un software de simulación para la industria de la aviación y necesita representar diferentes aeronaves como objetos. Pero antes de representar un avión, también debe definir las otras piezas de un avión como objetos. Elijamos tres: alas, cabina y motor. Ahora diga que el primer avión que quiere representar es el poderoso F-16. Probablemente escribirá tres clases, una para cada pieza específica del F-16. En su código, probablemente consumirá estas tres clases recién creadas como esta:

Este fragmento de código parece inofensivo, pero puede causar graves dolores de cabeza si su software de simulación despega y necesita expandirlo a otras aeronaves. Estos son algunos de los problemas con el código anterior:

  • Las clases concretas para las tres partes han sido expuestas al consumidor directamente.
  • El F-16 tiene varias variantes con diferentes motores. Si desea devolver un objeto de motor que coincida con la variante, deberá crear una subclase de la clase F-16Engine, lo que también requeriría cambiar el fragmento de código del consumidor.
  • La Lista en el fragmento de código se declara parametrizada con una clase concreta; si agrega otro motor de aeronave a su programa, no será reconocido por la Lista, aunque los motores para todas las aeronaves sean algo similares.

Tratar una interfaz como un concepto abstracto en lugar de una implementación

Un buen diseño orientado a objetos significa ocultar las clases concretas utilizadas en su aplicación y exponer las interfaces a los clientes. Un objeto responde a un conjunto de solicitudes que pueden ser capturadas por una interfaz implementada por la clase del objeto. Los clientes deben saber a qué demandas responde un objeto en lugar de la implementación.

En nuestro ejemplo, crearemos una interfaz que expone un método llamado start() . La clase F16Engine cambiará así:

Vea cómo cambia el código de consumidor correspondiente con el cambio anterior.

El código del consumidor ahora está libre de los detalles de implementación de cómo funciona el motor F-16 y qué clase lo implementa. Sin embargo, todavía no queremos revelar la parte F16Engine() del código. Queremos que nuestros consumidores sigan adivinando qué clase estamos instanciando. Esto se discute a continuación.

Creando una fábrica

En lugar de crear objetos en el código del cliente, tendremos una clase de fábrica que crea los objetos solicitados y los devuelve al cliente. Llamaremos a esta clase F16Factory porque puede producir varias partes del avión militar F16 y entregarlas al cliente que las solicita. La clase tomaría la siguiente forma.

Supongamos ahora que pasamos el objeto F16Factory al código del cliente como argumento del constructor, y luego puede crear objetos así:

Tenga en cuenta cómo esta configuración nos permite cambiar la clase concreta que representa el F16Engine siempre que se comprometa con la interfaz de IEngine . Podemos renombrar, mejorar o modificar nuestra clase sin causar un cambio radical en el cliente. También tenga en cuenta que al cambiar solo la clase de fábrica pasada al constructor del cliente, podemos proporcionar al cliente diferentes piezas para un avión completamente nuevo.

Fábrica de Fábricas: Fábrica grande que hace otras fábricas

¿No sería fantástico utilizar el mismo fragmento de cliente para diferentes tipos de aviones, como un Boeing 747 o un MiG-29 ruso ? Si todas las fábricas que implementan createEngine() acuerdan una interfaz estándar, nuestro código de cliente seguirá funcionando para todas las fábricas de aeronaves. Pero todas las fábricas tendrían que comprometerse con esta interfaz común cuyos métodos implementarán, y esta interfaz familiar es una fábrica abstracta .

Implementación

Comencemos con una interfaz que define los métodos que las diferentes fábricas de aeronaves deben implementar. El código del cliente se escribe en esta interfaz, pero se compondrá en tiempo de ejecución con una fábrica concreta.

Cuando nos referimos a " interfaz ", nos referimos a una interfaz Java o una clase abstracta. En este caso, podríamos haber usado una clase abstracta si hubiera implementaciones predeterminadas para cualquiera de los productos. Los métodos de creación no devuelven productos concretos: devuelven interfaces para desacoplar los consumidores de fábrica de la implementación concreta de las piezas.

La definición formal del patrón de fábrica abstracto dice que es un patrón de diseño que define una interfaz para crear familias de productos relacionados sin especificar las clases concretas. En este caso, IAircraftFactory es esa interfaz, y sus métodos de creación no devuelven partes sustanciales sino interfaces implementadas por las clases de partes concretas.

A continuación, configuremos las fábricas para nuestros dos aviones.

Una fábrica de hormigón producirá un motor, cabina y alas específicos de F-16 o Boeing. Cada parte tiene una interfaz de producto correspondiente que no enumeramos aquí por motivos de brevedad. Las interfaces de producto que representan las partes serían:

  • IEngine
  • ICockpit
  • IWings

En el artículo anterior , creamos una clase para F-16 que incluía un método fly() . Este método invocó a makeF16() , que construyó una instancia de F-16 y luego invocó a taxi() . Una vez que estuvo en la pista, se fue volando. Podemos aplicar este mismo enfoque a nuestro ejemplo del Boeing-747. Todos los aviones siguen el mismo patrón: fabricarse, rodar por la pista y luego volar. Así podemos crear una clase para un avión que hace estas tres tareas. Tenga en cuenta que no estamos creando clases separadas para representar los dos aviones (es decir, F-16 y Boeing-747), sino una sola clase de avión que puede representar a ambos.

Por el momento, mantendremos vacío el método makeAircraft . Entonces, primero veamos cómo un cliente solicitará objetos F-16 y Boeing-747.

Para agregar un constructor a nuestra clase Aircraft , necesitaremos crear una nueva variable de instancia llamada factory. La fábrica almacena el objeto que se encargará de crear las piezas de los aviones. Podemos obtener un avión diferente al componer el objeto del avión con otra fábrica. La versión completa de la clase Aircraft está a continuación:

El cliente debe instanciar la fábrica correcta, que pasa a la clase Aeronave . La clase Aircraft representa al cliente. Podríamos haber creado una interfaz IAircraft para representar todos los aviones que nuestra fábrica puede crear, pero para nuestro ejemplo limitado, es innecesario.

El código resultante se amplía y modifica fácilmente.

Para continuar con nuestro ejemplo del artículo del patrón del método de fábrica , podemos usar una subclase de la clase F16Factory para crear fábricas para las variantes A y B de F-16 o parametrizarlo con una enumeración que especifique el modelo de variante y devolver la parte correcta en un interruptor. declaración.

Otros ejemplos

  • El patrón de fábrica abstracto ayuda a construir familias de productos relacionados. Por ejemplo, si su biblioteca proporciona widgets de interfaz de usuario sofisticados y es compatible con macOS y Windows, es posible que necesite una familia de productos que funcionen en esas plataformas. Del mismo modo, los temas utilizados en los IDE pueden ser otro ejemplo. Por ejemplo, si su IDE admite temas claros y oscuros, puede usar el patrón de fábrica abstracto para crear widgets que pertenezcan al tema claro u oscuro variando la fábrica concreta que crea los widgets.
  • javax.xml.parsers.DocumentBuilderFactory.newInstance()le devolverá una fábrica.
  • javax.xml.transform.TransformerFactory.newInstance()le devolverá una fábrica.
  • El método de fábrica y el patrón de fábrica abstracto son similares en el sentido de que ayudan a gestionar la creación de objetos. La diferencia entre los dos patrones radica en sus motivaciones. El patrón de método de fábrica generalmente es responsable de crear un solo producto, mientras que un patrón de fábrica abstracto crea familias completas de productos relacionados. Además, usamos la herencia en el patrón del método de fábrica para construir productos más especializados. Finalmente, practicamos la composición de objetos en un patrón de fábrica abstracto pasando fábricas consumidas para crear los resultados deseados.
  • Creamos una fábrica de hormigón cuando añadimos un nuevo avión a nuestra flota. Pero supongamos que se agrega un helicóptero a la flota y requiere una parte que no tiene un avión. En ese caso, necesitaremos extender la interfaz IAircraftFactory con otro método creativo para la parte que solo requiere el helicóptero. Esto provocará el cambio en cascada a las fábricas existentes que necesitan devolver nulo ya que el nuevo componente no es parte de los jets.
  • Las fábricas de hormigón se implementan mejor como objetos singleton.