Patrón de fábrica abstracto
El patrón de software de fábrica abstracta proporciona una forma de encapsular un grupo de fábricas individuales que tienen un tema común sin especificar sus clases concretas. En el uso normal, el software del cliente crea una implementación concreta de la fábrica abstracta y luego usa la interfaz genérica de la fábrica para crear los objetos concretos que forman parte del tema. El cliente no sabe qué objetos concretos recibe de cada una de estas fábricas internas, ya que utiliza únicamente las interfaces genéricas de sus productos. 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 fábrica.
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 clase DocumentCreator
como FancyDocumentCreator
o ModernDocumentCreator
, cada una con una implementación diferente de createLetter()
y createResume()
que crearían objetos correspondientes como FancyLetter
o ModernResume
. Cada uno de estos productos se deriva de una clase abstracta simple como Letter
o Resume
que el cliente conoce. 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 implementación de DocumentCreator
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.
Una fábrica es la ubicación de una clase concreta en el código en el 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 tener que depender de sus clases concretas. Esto permite que se introduzcan nuevos tipos derivados sin cambios en el código que usa la clase base.
El uso de este patrón permite implementaciones concretas intercambiables sin cambiar el código que las usa, incluso en tiempo de ejecución. Sin embargo, el empleo de este patrón, al igual que con patrones de diseño similares, puede generar una complejidad innecesaria y un trabajo adicional en la escritura inicial del código. Además, los niveles más altos de separación y abstracción pueden dar como resultado sistemas que son más difíciles de depurar y mantener.
Resumen
El patrón de diseño de fábrica abstracto es uno de los 23 patrones conocidos descritos en el libro Design Patterns de 1994. Se puede utilizar para resolver problemas como:
- ¿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 requiere los objetos es inflexible porque hacerlo compromete la clase con objetos particulares y hace que sea imposible cambiar la creación de instancias más adelante independientemente de la clase sin tener que cambiarla. Evita que la clase sea reutilizable si se requieren otros objetos y hace que la clase sea difícil de probar porque los objetos reales no se pueden reemplazar con objetos ficticios.
El patrón describe cómo resolver tales problemas:
- Encapsular la creación de objetos en un objeto separado (factorial) 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 se puede configurar con un objeto de fábrica, que utiliza para crear objetos, y el objeto de fábrica se puede intercambiar en tiempo de ejecución.
Definición
El libro Patrones de diseño describe el patrón de fábrica abstracto como "una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas."
Uso
La fábrica determina el tipo concreto real de objeto que se creará, y es aquí donde se crea realmente el objeto (en Java, por ejemplo, mediante el operador nuevo). Sin embargo, la fábrica solo devuelve un puntero abstracto al objeto concreto creado.
Esto aísla el código del cliente de la creación de objetos haciendo que los clientes soliciten que un objeto de fábrica cree un objeto del tipo abstracto deseado y devuelva un puntero abstracto al objeto.
Como la fábrica solo devuelve un puntero abstracto, el código de cliente que solicitó el objeto de la fábrica no tiene conocimiento del tipo concreto real del objeto que se creó, y no se ve afectado por él. Sin embargo, el tipo de un objeto concreto (y por lo tanto de una fábrica concreta) es conocido por la fábrica abstracta; por ejemplo, la fábrica puede leerlo desde un archivo de configuración. El cliente no necesita especificar el tipo, ya que el tipo ya se ha especificado en el archivo de configuración. En particular, esto significa:
- El código cliente no tiene ningún conocimiento del tipo de hormigón, sin necesidad de incluir ningún fichero de cabecera o declaraciones de clase relacionadas con él. El código del cliente se ocupa únicamente del tipo abstracto. Los objetos de un tipo concreto son creados por la fábrica, pero el código del cliente accede a tales objetos sólo a través de sus interfaces abstractas.
- La adición de nuevos tipos de hormigón se realiza modificando el código del cliente para utilizar una fábrica diferente, una modificación que es típicamente una línea en un archivo. La fábrica diferente entonces crea objetos de un tipo de hormigón diferente pero todavía devuelve un puntero del igual tipo abstracto como antes, aislante el código cliente del cambio. Esto es significativamente más fácil que modificar el código del cliente para instantánear un nuevo tipo, lo que requeriría cambiar cada ubicación en el código donde se crea un nuevo objeto, así como asegurar que todas las ubicaciones de código tengan conocimiento del nuevo tipo de hormigón, por ejemplo, incluyendo un archivo de cabecera de clase concreto. Si todos los objetos de fábrica se almacenan globalmente en un objeto singleton, y todo el código de cliente pasa a través del singleton para acceder a la fábrica adecuada para la creación de objetos, entonces cambiar fábricas es tan fácil como cambiar el objeto singleton.
Estructura
Diagrama UML
En el diagrama de clases UML anterior,
la clase Client
que requiere los objetos ProductA
y ProductB
no instancia ProductA1
y ProductB1
clases directamente. En su lugar, Client
se refiere a la interfaz AbstractFactory
para crear objetos, lo que hace que Client
sea independiente de cómo se crean los objetos (qué clases concretas son instanciado). La clase Factory1
implementa la interfaz AbstractFactory
instanciando las clases ProductA1
y ProductB1
.
El diagrama de secuencia UML muestra las interacciones en tiempo de ejecución. El objeto Client
llama a createProductA()
en el objeto Factory1
, que crea y devuelve un objeto ProductA1
. A partir de entonces, Client
llama a createProductB()
en Factory1
, que crea y devuelve un objeto ProductB1
.
Gráfico LePUS3
Ejemplo de Python
desde abc importación ABC, abstractodesde Sys importación plataformaclase Button()ABC): @abstractmethod def pintura()auto): pasoclase LinuxButton()Button): def pintura()auto): retorno "Render un botón en un estilo Linux"clase WindowsButton()Button): def pintura()auto): retorno "Render un botón en un estilo de Windows"clase MacOSButton()Button): def pintura()auto): retorno "Render un botón en un estilo MacOS"clase GUI Factory()ABC): @abstractmethod def create_button()auto): pasoclase LinuxFactory()GUI Factory): def create_button()auto): retorno LinuxButton()clase WindowsFactory()GUI Factory): def create_button()auto): retorno WindowsButton()clase MacOSFactory()GUI Factory): def create_button()auto): retorno MacOSButton()si plataforma == "linux": fábrica = LinuxFactory()Elif plataforma == "darwin": fábrica = MacOSFactory()Elif plataforma == "win32": fábrica = WindowsFactory()más: aumento No Implemented Error()f"No implementado para su plataforma: {}plataforma}")botón = fábrica.create_button()resultado = botón.pintura()impresión()resultado)
Implementación alternativa utilizando las propias clases como fábricas:
desde abc importación ABC, abstractodesde Sys importación plataformaclase Button()ABC): @abstractmethod def pintura()auto): pasoclase LinuxButton()Button): def pintura()auto): retorno "Render un botón en un estilo Linux"clase WindowsButton()Button): def pintura()auto): retorno "Render un botón en un estilo de Windows"clase MacOSButton()Button): def pintura()auto): retorno "Render un botón en un estilo MacOS"si plataforma == "linux": fábrica = LinuxButtonElif plataforma == "darwin": fábrica = MacOSButtonElif plataforma == "win32": fábrica = WindowsButtonmás: aumento No Implemented Error()f"No implementado para su plataforma: {}plataforma}")botón = fábrica()resultado = botón.pintura()impresión()resultado)
Contenido relacionado
Transporte en el Reino Unido
Relación de compresión de datos
Transporte en República Dominicana