Patrón de diseño de método de plantilla en Python

En esta guía, veremos la teoría y la implementación del método de plantilla, un patrón de diseño de comportamiento, en Python.

Introducción

El proceso de ingeniería de software a menudo nos revela muchos elementos redundantes dentro de la estructura y el código de nuestro software. Sabiendo esto, algunas de las principales responsabilidades de un desarrollador son escribir código fácilmente comprensible y mantenible, no solo soluciones.

Sin embargo, los proyectos a menudo se vuelven más complejos con el tiempo, lo que hace que la estructura de software inicial sea un elemento crítico que debe estar bien pensado e implementado desde el principio.

Los patrones de diseño están destinados a resolver este problema.

Los patrones de diseño son un conjunto de estándares utilizados para optimizar tareas específicas relacionadas con el paradigma Programación Orientada a Objetos (POO). Su objetivo es reducir el número total de líneas de código, optimizar las estructuras y estandarizar la arquitectura del software.

El paradigma de la Programación Orientada a Objetos ofrece una estructura basada en clases, donde cada clase representa un modelo para un objeto (instancia de esa clase) que tiene sus propios atributos y métodos. Estas clases están relacionadas y tienen sus propias dependencias, composiciones, herencia, etc. Traducir problemas y estructuras de la vida real a soluciones de software es la principal motivación para implementar esta estructura.

En esta guía, exploraremos uno de los Patrones de diseño de comportamiento y su implementación en Python: el Método de plantilla.

Este patrón de diseño nos dará un método general compuesto de múltiples pasos. Las clases relacionadas con nuestra clase de método de plantilla pueden llamar a estos pasos individualmente o sobrescribirlos.

Además, aprenderemos a usar la Biblioteca ABC en Python, que define una relación de herencia para las clases base abstractas (ABC). Usaremos esta biblioteca para crear un ejemplo de método de plantilla simple.

Patrones de diseño de comportamiento

El Método de plantilla es un Patrón de diseño de comportamiento. ¿Qué es exactamente un patrón de diseño de comportamiento?

Hay tres patrones de diseño de software ampliamente reconocidos: Creativo, Estructural y Comportamental.

  • Los patrones de diseño creativo están destinados a permitir la creación de objetos mientras se abstrae/oculta la lógica de creación del objeto. Los patrones de diseño creacional se utilizan para dar a los programas una mayor flexibilidad en la selección de los objetos que deben crear para cualquier caso de uso.

  • Los patrones de diseño estructural están destinados a manejar la composición de objetos y clases, confiando en la herencia para controlar cómo se crean los objetos y cómo se les asignan funciones.

  • Patrones de diseño de comportamiento se centran en la comunicación que se produce entre objetos, controlando cómo se mueven los datos entre objetos y distribuyendo el comportamiento entre clases.

El método de plantilla se centra en la distribución de funciones y métodos entre clases.

El patrón de diseño del método de plantilla

El patrón de diseño del método de plantilla nos permite crear una clase base que contiene algunos pasos necesarios para completar un proceso. Cuando define estos pasos con una plantilla, es posible crear una o más clases concretas y sobrescribir los pasos de la plantilla. Esto le permite implementar algunos o todos los pasos, según la clase concreta, sin sobrescribir todo el proceso.

Para usar el método de plantilla, necesitamos una clase abstracta. La clase abstracta es esencialmente un proceso principal dividido en pasos más pequeños o procesos menores. Dicho de otro modo, la clase abstracta utilizará el método de plantilla (el proceso principal) y dentro de la plantilla encontraremos llamadas a los pasos menores que completan el proceso principal. Estos procesos menores serán métodos/funciones que las clases concretas pueden llamar.

El uso de una clase abstracta significa que no tenemos que instanciar toda la clase base para acceder a los pasos definidos con el método de plantilla. En cambio, podemos crear subclases a partir de la clase abstracta y sobrescribir solo los pasos que necesitamos en las subclases individuales.

Una vez que definimos la clase abstracta, podemos crear las clases concretas que sobrescribirán los pasos que necesitamos. Usaremos una relación de herencia para lograr esto. Dependiendo del contexto de la clase concreta, sobrescribiremos todos los pasos o solo algunos de ellos.

Podemos representar la estructura del método plantilla con un diagrama de clases como este, de acuerdo con el paradigma OOP:

UML diagram of the template method design pattern

Aquí puede ver que comenzamos creando una clase abstracta con un método de plantilla que consta de varios pasos/funciones. A partir de esta clase abstracta, creamos dos clases concretas que utilizan diferentes pasos del método de plantilla.

El método de plantilla frente al método de fábrica

Existe cierta confusión en cuanto a las diferencias entre el método Template Pattern y el Patrón de método de fábrica. Esto se debe a que su estructura es similar, aunque no son lo mismo. El método de fábrica es un patrón creativo que se utiliza para crear objetos a partir de una superclase. Por el contrario, el Método Plantilla es un Patrón de Comportamiento utilizado para definir un método general compuesto de pasos que pueden ser modificados por subclases de la clase abstracta, que contiene el método plantilla.

En otras palabras, mientras que el Método de fábrica crea objetos, el Método de plantilla sobrescribe las funcionalidades de un proceso principal/base.

Ahora que hemos aclarado la diferencia entre estos patrones, podemos explorar cómo implementar el Patrón de diseño de método de plantilla en Python.

Nota: Python no admite clases abstractas sin usar una biblioteca específica. Para usar asociaciones de clases abstractas, necesitamos importar la biblioteca A B C.

La biblioteca ABC

La biblioteca ABC proporciona infraestructura para administrar clases base abstractas en Python. Esto significa que podemos crear relaciones de clase como herencia o implementaciones para clases abstractas, algo necesario para implementar la mayoría de los patrones de diseño y particularmente importante en el caso del Método de plantilla.

¿Cuándo usar el patrón del método de plantilla?

Querrá usar el Método de plantilla cuando necesite usar o modificar, algunos o todos los pasos de un algoritmo. En estos casos, deberá diferenciar los pasos de su algoritmo o proceso, haciéndolos accesibles individualmente a través de herencia o implementación.

Veamos un ejemplo práctico:

Tenemos dos grupos de investigadores, uno de la Universidad A y otro de la Universidad B. Estos dos grupos están estudiando los efectos de la cuarentena, implementada por los gobiernos en respuesta a la pandemia del SARS‑CoV‑2. Ambos grupos tienen el mismo proceso de investigación de base. El proceso de investigación básico es una plantilla que los dos grupos de investigación pueden utilizar para continuar con su investigación. Sin embargo, los grupos de investigación pueden personalizar el proceso de investigación en términos de:

  • Qué pasos se llevan a cabo durante la investigación.
  • Cómo se lleva a cabo cada paso de la investigación.

Representemos esta investigación con un diagrama de clases antes de crear el modelo con código Python.

La guía de investigación se compone de 4 pasos:

  • La Universidad A decide aplicar 2 de los 4 pasos (2 y 3)
  • La Universidad B aplica el 3 de los pasos (1, 3 y 4)
  • Ambos grupos modificaron todos los pasos elegidos.
  • Finalmente, se debe aplicar el paso número 3 para ambos grupos ya que es obligatorio.

Ya tenemos nuestra clase de diagrama, solo tenemos que cambiarla para que encaje con nuestro problema.

Al actualizar el diagrama para que se ajuste a las condiciones que especificamos, obtenemos el siguiente modelo:

UML diagram - Python implementation of template method design pattern

Implementación del patrón de diseño del método de plantilla en Python {#implementación del patrón de diseño del método de plantilla en Python}

Ahora que tenemos el esquema de nuestras clases abstractas y concretas, implementémoslas en Python.

Comencemos con nuestra clase abstracta: researchGuideline.py, que contendrá nuestros métodos de plantilla con los cuatro pasos principales para la investigación.

Primero, importaremos la biblioteca ABC. Esta biblioteca contiene una clase llamada ABC, y la usaremos como una superclase para nuestra plantilla de investigación, convirtiéndola en una clase base abstracta.

A continuación, definiremos nuestros pasos como métodos de clase. Estos métodos estarán vacíos por ahora, pero cuando definamos las subclases, sobrescribirán los pasos:

 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
# Importing the ABC library
from abc import ABC, abstractmethod

# Creating our abstract class:
class ResearchGuideline(ABC):
    
    # Template Method definition:
    def templateMethod(self):
        # Calling all the steps
        self.step1()
        self.step2()
        self.step3()
        self.step4()
        
    # Defining the Template Method Steps
    def step1(self):
        pass

    def step2(self):
        pass
    
    @abstractmethod
    def step3(self):
        pass

    def step4(self):
        pass

Observe cómo agregamos el decorador @abstractmethod al paso 3. Esto muestra que las subclases de una clase abstracta siempre deben sobrescribir ese método. Este decorador debemos incluirlo en las importaciones, ya que también forma parte de la biblioteca ABC.

Definamos nuestras clases concretas ahora. Estamos hablando de Universidades A y B, con sus respectivos pasos. Crearemos una subclase para ambas universidades utilizando la plantilla ResearchGuideline.

Para ambas clases, tenemos que importar la clase ResearchGuideline y crear una herencia entre la superclase y la subclase. Esto nos permite usar el paso que definimos en la guía/plantilla y sobrescribirlos. La aplicación de los pasos será un simple log/print en este caso.

Comencemos con la primera subclase:

1
2
3
4
5
6
7
8
from researchGuideline import ResearchGuideline

class UniversityA(ResearchGuideline):
    def step2(self):
        print("Step 2 - Applied by University A")
    
    def step3(self):
        print("Step 3 - Applied by University A")

Guardaremos esto en un archivo Python llamado universityA. Ahora configuremos la segunda subclase:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from researchGuideline import ResearchGuideline

class UniversityB(ResearchGuideline):
    def step1(self):
        print("Step 1 - Applied by University B")
    
    def step3(self):
        print("Step 3 - Applied by University B")

    def step4(self):
        print("Step 4 - Applied by University B")

Guardaremos esto en un archivo Python llamado universityB.

Tenga en cuenta que hemos indicado qué universidad está aplicando qué pasos. Esto nos ayuda a apreciar la variación entre las dos clases concretas.

¡Nuestro modelo del método de plantilla, incluidas la clase abstracta y las clases concretas, está listo! Ahora vamos a crear nuestro script de cliente, para que podamos aplicar el modelo.

Primero, importemos nuestras clases. Esto implica importar la clase abstracta y las dos clases concretas. Luego, crearemos una función que reciba un objeto ResearchGuideline como parámetro, que es nuestra plantilla/clase abstracta.

Aquí está la belleza de la relación de herencia, porque las clases universitarias son subclases de ResearchGuideline y comparten el mismo tipo de objeto.

Podemos pasar el objeto UniversidadA o UniversidadB como argumento a nuestra función que llama al método de plantilla (esto es client_call() a continuación), y los pasos sobrescritos por la clase concreta cambiarán la forma en que se ejecuta el método de plantilla.

Aquí usamos ambas clases, por lo que podemos comparar los resultados:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Imports
from researchGuideline import *
from universityA import UniversityA
from universityB import UniversityB

# Auxiliary function
def client_call(research_guideline: ResearchGuideline):
    research_guideline.templateMethod();

# Entry point
if __name__ == '__main__':
    # Calling the Template Method using the University A class as parameter
    print("University A:")
    client_call(UniversityA())
    
    # Calling the Template Method using the University A class as parameter
    print("University B:")
    client_call(UniversityB())

Ejecutando este código, obtenemos el siguiente resultado:

1
2
3
4
5
6
7
University A:
Step 2 - Applied by University A
Step 3 - Applied by University A
University B:
Step 1 - Applied by University B
Step 3 - Applied by University B
Step 4 - Applied by University B

Conclusión

El método de plantilla es una forma efectiva de distribuir tareas entre clases, redefinir procesos y reducir código. La aplicación de este patrón de diseño a un algoritmo o solución puede ayudarlo a evitar métodos redundantes y optimizar los procesos de ejecución más largos.

El método de la plantilla también es un ejemplo del uso adecuado del paradigma OOP. El modelo no se puede aplicar en todos los casos, así que asegúrese de comprender las necesidades de su proyecto antes de usarlo. rlo.