Patrón de diseño de fábrica abstracto en Python

El patrón de diseño de fábrica abstracta es un patrón de creación que proporciona un marco para crear interfaces de objetos estrechamente relacionados sin especificar sus clases.

Introducción

Los patrones de diseño ayudan a que el código sea comprensible, escalable y reutilizable. Existen diferentes categorías de patrones de diseño, dependiendo de los problemas que se aborden. Cuando el código está modularizado, es más fácil agregar funciones más nuevas sin realizar cambios drásticos en el proyecto.

El Patrón de diseño de fábrica abstracto es un patrón de creación que proporciona un marco para crear interfaces de objetos estrechamente relacionados sin especificar sus clases, de ahí el término "abstracto".

Una clase abstracta se implementa parcialmente y define los requisitos que deben tener sus clases secundarias y algunos comportamientos secundarios genéricos, así como las funciones que deben tener. Las clases concretas amplían las clases abstractas y proporcionan la funcionalidad no implementada, mientras heredan las funcionalidades comunes.

Clases abstractas en Python

Por ejemplo, todos los ‘Animales’ tienen una función ‘comer()’, comúnmente implementada entre todos los animales, pero cada uno tiene funciones únicas de ‘hablar()’, por lo que la clase abstracta ‘Animal’ deja eso en manos de los niños.

En Python, cada clase abstracta se deriva de la clase ABC del módulo abc. El método abstracto se declara dentro de la clase abstracta pero no se implementa, y todos los métodos implementados se transmiten a clases concretas.

En el siguiente ejemplo, observe que la definición AbstractClass contiene un decorador @abstractmethod. Al decorar una función con él, definimos que todas las clases secundarias deben tener una implementación del método func(), ya que no hay una implementación común predeterminada:

1
2
3
4
5
6
from abc import ABC, abstractmethod

class AbstractClass(ABC):
    @abstractmethod
    def func():
        pass

La implementación parcial de la clase de fábrica abstracta principal se realiza mediante su clase secundaria/concreta. Cada clase secundaria contendrá un método func() para cumplir con los requisitos de la clase abstracta.

A partir de nuestro ejemplo anterior, podemos crear una clase secundaria como esta:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from abc import ABC, abstractmethod

class AbstractClass(ABC):
    @abstractmethod
    def func(self):
        pass

class ChildClass(AbstractClass):
    def func(self):
        out = "This is an output"
        return out

obj = ChildClass()
print(obj.func())

Patrón de diseño de fábrica abstracto en Python

Volviendo a la definición de Abstract Factory Pattern:

A través de Abstract Factory Pattern, definimos interfaces para crear familias de objetos relacionados sin especificar sus clases concretas.

Por lo tanto, Abstract Factory Pattern delega la creación de objetos a otra clase. Este patrón es más adecuado cuando se desea crear múltiples categorías de un objeto abstrayendo su implementación.

Puede que estés familiarizado con el concepto fábricas: estos son objetos que crean otros objetos. Abstract Factory Pattern se ocupa principalmente de la interfaz para los objetos de fábrica.

¿Qué es una familia de objetos?

Una mesa puede ser redonda, cuadrada u ovalada. En un sentido similar, una clase común como ‘Empleado’ podría ramificarse a varias clases más concretas.

Aquí hay una representación visual del patrón Abstract Factory:

Visual Representation of the Abstract Factory Pattern in Python

Suponga que está diseñando una familia de dos productos (un navegador y un mensajero).

  • Productos abstractos: Se crean dos clases abstractas, una para el navegador y otra para el messenger. Estas clases contienen métodos abstractos que son obligatorios para la construcción de los productos. Estas clases abstractas se conocen como interfaces.

    In the example shown above, the Web Browser and Messenger are the abstract products.

  • Productos concretos: Los productos concretos heredan los métodos abstractos de las clases abstractas, es decir, los productos abstractos. Usando las interfaces, se pueden crear diferentes familias de productos.

    For example, in the diagram above, three different kinds of web browsers are created for three different sets of users. If there's one thing that all these concrete products have in common, that would be the abstract methods that were defined in the abstract class.

  • Fábricas de hormigón: Las fábricas de hormigón crean productos de hormigón según las instrucciones de las fábricas abstractas. Las fábricas concretas solo son capaces de crear aquellos productos que se especifican en ellas: una BrowserFactory crea navegadores, mientras que una MessengerFactory crea mensajeros. Alternativamente, puede concentrarse en algunas características comunes y decir: crear una BasicFactory y SecureFactory que crean navegadores web básicos o seguros e instancias de mensajería.

    In the diagram mentioned above, the Vanilla Products Factory is capable of creating both vanilla concrete products (browser and messenger), while the Secure Products Factory makes safe versions.

  • Fábricas abstractas: Las fábricas abstractas poseen interfaces para crear los productos abstractos, es decir, contienen varios métodos que devuelven productos abstractos.

    In the example, the interfaces of concrete factories are invoked to get the abstract products as a web browser and messenger.

Implementación

Familiarizados con la terminología, intentemos implementar el patrón Abstract Factory en Python.

En primer lugar, creamos los productos abstractos: Browser y Messenger como:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from abc import ABC, abstractmethod

class Browser(ABC):
    """
    Creates "Abstract Product A"
    """

    # Interface - Create Search Toolbar
    @abstractmethod
    def create_search_toolbar(self):
        pass

    # Interface - Create Browser Window
    @abstractmethod
    def create_browser_window(self):
        pass

class Messenger(ABC):
    """
    Creates "Abstract Product B"
    """

    @abstractmethod
    # Interface - Create Messenger Window
    def create_messenger_window(self):
        pass

Estos actúan como clases base para las siguientes clases concretas, es decir, variantes de productos de los productos abstractos: vainilla y seguro:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class VanillaBrowser(Browser):
    """
    Type: Concrete Product
    Abstract methods of the Browser base class are implemented.
    """

    # Interface - Create Search Toolbar
    def create_search_toolbar(self):
        print("Search Toolbar Created")

    # Interface - Create Browser Window]
    def create_browser_window(self):
        print("Browser Window Created")


class VanillaMessenger(Messenger):
    """
    Type: Concrete Product
    Abstract methods of the Messenger base class are implemented.
    """

    # Interface - Create Messenger Window
    def create_messenger_window(self):
        print("Messenger Window Created")

class SecureBrowser(Browser):
    """
    Type: Concrete Product
    Abstract methods of the Browser base class are implemented.
    """

    # Abstract Method of the Browser base class
    def create_search_toolbar(self):
        print("Secure Browser - Search Toolbar Created")

    # Abstract Method of the Browser base class
    def create_browser_window(self):
        print("Secure Browser - Browser Window Created")

    def create_incognito_mode(self):
        print("Secure Browser - Incognito Mode Created")


class SecureMessenger(Messenger):
    """
    Type: Concrete Product
    Abstract methods of the Messenger base class are implemented.
    """

    # Abstract Method of the Messenger base class
    def create_messenger_window(self):
        print("Secure Messenger - Messenger Window Created")

    def create_privacy_filter(self):
        print("Secure Messenger - Privacy Filter Created")

    def disappearing_messages(self):
        print("Secure Messenger - Disappearing Messages Feature Enabled")

Puede notar que además de los métodos abstractos, también se agregan características adicionales a los productos concretos para hacerlos funcionales en su propio contexto.

Estamos casi alli. Ahora vamos a crear la propia fábrica abstracta y las correspondientes fábricas concretas como:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class AbstractFactory(ABC):
    """
    The Abstract Factory
    """

    @abstractmethod
    def create_browser(self):
        pass

    @abstractmethod
    def create_messenger(self):
        pass

class VanillaProductsFactory(AbstractFactory):
    """
    Type: Concrete Factory
    Implement the operations to create concrete product objects.
    """

    def create_browser(self):
        return VanillaBrowser()

    def create_messenger(self):
        return VanillaMessenger()

class SecureProductsFactory(AbstractFactory):
    """
    Type: Concrete Factory
    Implement the operations to create concrete product objects.
    """

    def create_browser(self):
        return SecureBrowser()

    def create_messenger(self):
        return SecureMessenger()

En el código anterior, dado que AbstractFactory actúa como una clase base, los métodos abstractos se instancian tal como se menciona en la clase base.

Agreguemos un método main() para que podamos ver nuestro ejemplo en acción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def main():
    for factory in (VanillaProductsFactory(), SecureProductsFactory()):
        product_a = factory.create_browser()
        product_b = factory.create_messenger()
        product_a.create_browser_window()
        product_a.create_search_toolbar()
        product_b.create_messenger_window()

if __name__ == "__main__":
    main()

Si desea ver el archivo de código completo, puede encontrarlo en el repositorio [en GitHub] (https://github.com/wikihtp/abstract-factory-design-pattern-in-python).

Si ejecutamos nuestro código, devuelve el siguiente resultado que muestra la creación del navegador seguro y el mensajero seguro:

1
2
3
4
5
6
7
$ python3 abstract_factory_code.py
Browser Window Created
Search Toolbar Created
Messenger Window Created
Secure Browser - Browser Window Created
Secure Browser - Search Toolbar Created
Secure Messenger - Messenger Window Created

Ventajas y desventajas

Ahora que hemos implementado el patrón, analicemos sus ventajas y desventajas.

Pros:

  • La principal ventaja de este patrón es la flexibilidad: la capacidad de agregar características y funciones más nuevas a los productos existentes o quizás incluso agregar productos concretos más nuevos a las fábricas de concreto. Esto se puede hacer sin sabotear todo el código.

  • Existe una mínima interacción directa entre el cliente y los productos de hormigón. También hay flexibilidad en la organización y compactación del código.

Contras

  • El mayor inconveniente de este patrón es la legibilidad y mantenibilidad del código. Aunque proporciona una forma flexible de agregar nuevos futuros, agregar un nuevo componente requerirá agregar clases concretas, modificar las interfaces, etc. Los efectos en cascada de la modificación requieren tiempo de desarrollo.

Conclusión

Abstract Factory Pattern se puede usar de manera muy efectiva para familias estrechamente relacionadas de diferentes productos, a diferencia de Factory Pattern, que solo se puede usar para un solo tipo de producto.

If you are interested in learning more about it, read our guide to the Patrón de fábrica en Python.

The Abstract Factory Pattern resuelve una gran crisis por la necesidad de escribir código limpio. Hemos cubierto los conceptos básicos de este patrón y también hemos entendido la implementación mediante un ejemplo.