El patrón de diseño del método de fábrica en Python

En este tutorial, veremos un ejemplo y la implementación del patrón de diseño de método de fábrica en Python, junto con la motivación y la definición.

Introducción

En este artículo, nos sumergiremos en el patrón de diseño del método de fábrica, implementado en Python.

Los patrones de diseño definen soluciones probadas y verificadas para varios problemas recurrentes en el desarrollo de software. No representan código real, sino formas en las que podemos organizar nuestro código para obtener resultados óptimos.

En un mundo de recursos limitados, los patrones de diseño nos ayudan a lograr la mayor cantidad de resultados con la menor cantidad de recursos utilizados. También es importante tener en cuenta que los patrones de diseño no se aplican a todas las situaciones y es crucial evaluar el problema en cuestión para elegir el mejor enfoque para ese escenario en particular.

Los patrones de diseño se dividen en unas pocas categorías amplias, aunque principalmente en patrones de creación, patrones estructurales y patrones de comportamiento.

El patrón Factory Method es un patrón de diseño creativo.

El patrón de diseño del método de fábrica

Definición

El método de fábrica se utiliza en la programación orientada a objetos como un medio para proporcionar interfaces de fábrica para crear objetos. Estas interfaces definen la estructura genérica, pero no inicializan objetos. La inicialización se deja a subclases más específicas.

La clase/interfaz principal alberga todo el comportamiento estándar y genérico que se puede compartir entre subclases de diferentes tipos. La subclase es a su vez responsable de la definición e instanciación del objeto basado en la superclase.

Motivación

La motivación principal detrás del patrón de diseño de método de fábrica es mejorar el acoplamiento flexible en el código mediante la creación de una clase abstracta que se usará para crear diferentes tipos de objetos que comparten algunos atributos y funcionalidades comunes.

Esto da como resultado una mayor flexibilidad y reutilización del código porque la funcionalidad compartida no se reescribirá al haber sido heredada de la misma clase. Este patrón de diseño también se conoce como Constructor virtual.

El patrón de diseño Factory Method se usa comúnmente en bibliotecas al permitir que los clientes elijan qué subclase o tipo de objeto crear a través de una clase abstracta.

Un método de fábrica recibirá información sobre un objeto requerido, lo instanciará y devolverá el objeto del tipo especificado. Esto le da a nuestra aplicación o biblioteca un único punto de interacción con otros programas o piezas de código, encapsulando así nuestra funcionalidad de creación de objetos.

Implementación del método de fábrica {#implementación del método de fábrica}

Nuestro programa será una biblioteca utilizada para manejar objetos de forma en términos de creación y otras operaciones, como agregar color y calcular el área de la forma.

Los usuarios deberían poder usar nuestra biblioteca para crear nuevos objetos. Podemos comenzar creando formas individuales únicas y utilizarlas tal como están, pero eso significaría que mucha de la lógica compartida tendrá que reescribirse para todas y cada una de las formas que tenemos disponibles.

El primer paso para resolver esta repetición sería crear una clase de forma principal que tenga métodos como calculate_area() y calculate_perimeter(), y propiedades como dimensiones.

Los objetos de forma específicos se heredarán de nuestra clase base. Para crear una forma, necesitaremos identificar qué tipo de forma se requiere y crear la subclase para ella.

Comenzaremos creando una clase abstracta para representar una forma genérica:

1
2
3
4
5
6
7
8
9
import abc
class Shape(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def calculate_area(self):
        pass

    @abc.abstractmethod
    def calculate_perimeter(self):
        pass

Esta es la clase base para todas nuestras formas. Avancemos y creemos varias formas concretas y más específicas:

 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
class Rectangle(Shape):
    def __init__(self, height, width):
        self.height = height
        self.width = width

    def calculate_area(self):
        return self.height * self.width 

    def calculate_perimeter(self):
        return 2 * (self.height + self.width) 

class Square(Shape):
    def __init__(self, width):
        self.width = width

    def calculate_area(self):
        return self.width ** 2

    def calculate_perimeter(self):
        return 4 * self.width

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def calculate_area(self):
        return 3.14 * self.radius * self.radius

    def calculate_perimeter(self):
        return 2 * 3.14 * self.radius

Hasta ahora, hemos creado una clase abstracta y la hemos ampliado para adaptarse a diferentes formas que estarán disponibles en nuestra biblioteca. Para crear los objetos de diferentes formas, los clientes deberán conocer los nombres y detalles de nuestras formas y realizar la creación por separado.

Aquí es donde entra en juego el Método de Fábrica.

El patrón de diseño de Factory Method nos ayudará a abstraer las formas disponibles del cliente, es decir, el cliente no tiene que conocer todas las formas disponibles, sino que solo debe crear las que necesita durante el tiempo de ejecución. También nos permitirá centralizar y encapsular la creación de objetos.

Logremos esto creando una ShapeFactory que se usará para crear las clases de forma específicas basadas en la entrada del cliente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class ShapeFactory:
    def create_shape(self, name):
        if name == 'circle':
            radius = input("Enter the radius of the circle: ")
            return Circle(float(radius))

        elif name == 'rectangle':
            height = input("Enter the height of the rectangle: ")
            width = input("Enter the width of the rectangle: ")
            return Rectangle(int(height), int(width))

        elif name == 'square':
            width = input("Enter the width of the square: ")
            return Square(int(width))

Esta es nuestra interfaz para la creación. No llamamos a los constructores de clases concretas, llamamos a Factory y le pedimos que cree una forma.

Nuestra ShapeFactory funciona recibiendo información sobre una forma, como un nombre y las dimensiones requeridas. Nuestro método de fábrica create_shape() se utilizará para crear y devolver objetos listos de las formas deseadas.

El cliente no tiene que saber nada sobre la creación del objeto o detalles. Usando el objeto de fábrica, pueden crear objetos con un conocimiento mínimo de cómo funcionan:

1
2
3
4
5
6
7
8
9
def shapes_client():
    shape_factory = ShapeFactory()
    shape_name = input("Enter the name of the shape: ")

    shape = shape_factory.create_shape(shape_name)

    print(f"The type of object created: {type(shape)}")
    print(f"The area of the {shape_name} is: {shape.calculate_area()}")
    print(f"The perimeter of the {shape_name} is: {shape.calculate_perimeter()}")

Ejecutar este código dará como resultado:

1
2
3
4
5
6
Enter the name of the shape: circle
Enter the radius of the circle: 7

The type of object created: <class '__main__.Circle'>
The area of the circle is: 153.86
The perimeter of the circle is: 43.96

O bien, podríamos construir otra forma:

1
2
3
4
5
6
Enter the name of the shape: square
Enter the width of the square: 5

The type of object created: <class '__main__.Square'>
The area of the square is: 25
The perimeter of the square is: 20

Lo que vale la pena señalar es que, además de que el cliente no tiene que saber mucho sobre el proceso de creación, cuando nos gustaría crear una instancia de un objeto, no llamamos al constructor de la clase. Le pedimos a la fábrica que haga esto por nosotros según la información que le pasamos a la función create_shape().

Ventajas y desventajas

Ventajas

Una de las principales ventajas de usar el patrón de diseño Factory Method es que nuestro código se acopla de forma flexible, ya que la mayoría de los componentes de nuestro código desconocen otros componentes de la misma base de código.

Esto da como resultado un código que es fácil de entender y probar y agrega más funcionalidad a componentes específicos sin afectar o romper todo el programa.

El patrón de diseño de Factory Method también ayuda a mantener el Principio de responsabilidad única donde las clases y los objetos que manejan funcionalidades específicas dan como resultado un mejor código.

Contras

La creación de más clases eventualmente conduce a una menor legibilidad. Si se combina con una fábrica abstracta (fábrica de fábricas), el código pronto se volverá detallado, sin embargo, mantenible.

Conclusión

En conclusión, el patrón de diseño del método de fábrica nos permite crear objetos sin especificar la clase exacta requerida para crear el objeto en particular. Esto nos permite desacoplar nuestro código y mejora su reutilización.

Es importante tener en cuenta que, al igual que cualquier otro patrón de diseño, solo es adecuado para situaciones específicas y no para todos los escenarios de desarrollo. Una evaluación de la situación actual es crucial antes de decidir implementar el patrón de diseño del método de fábrica para obtener los beneficios del patrón.