Patrones de diseño VS Principios de diseño:método de plantilla

Patrones de diseño VS Principios de diseño:método de plantilla

En el episodio de hoy de la serie "Patrones de diseño VS Principios de diseño", relacionamos el Método de plantilla patrones de diseño a principios de diseño más generales. Estamos mostrando cómo se relaciona con el polimorfismo principio de diseño.

El GoF se encuentra con el GRASP

Si acaba de unirse a nosotros en la serie, esto es de lo que se trata:estamos repasando cada uno de los 23 patrones de diseño que provienen del libro de patrones de diseño seminal de GoF, y para cada patrón estamos tratando de ver a qué GRASP principio de diseño con el que más se relaciona.

GRASP son principios de diseño de alto nivel que se explican en el libro Aplicación de UML y patrones de Craig Larman.

Los principios GRASP se ven así (excluyendo "Gestión de la complejidad", jerarquía mía):

El propósito de esta serie es:

  • comprender la esencia de cada patrón de diseño de GoF,
  • comprender mejor el principio de diseño GRASP,
  • poder tomar mejores decisiones a la hora de estructurar nuestro código, para hacerlo más expresivo y robusto.

Hoy, nos enfocamos en el método de plantilla .

Método de plantilla

En primer lugar, mencionemos que el patrón de diseño del método de plantilla no tiene nada que ver con las plantillas de C++. En realidad, hay una implementación de este patrón de diseño en C++ que usa plantillas, pero el uso de plantillas es más un detalle de implementación que la esencia de este patrón.

Template Method no tiene nada que ver con C++ en particular, y se puede implementar en otros lenguajes que no admiten plantillas.

Método de plantilla consiste en tener una pieza de código que tiene uno o más puntos de personalización .

Por ejemplo, en el siguiente fragmento de código:

doX();
doY();
doZ();

El método de plantilla puede consistir en hacer doY() personalizable.

Los ejemplos en los que esto es útil son innumerables. Un ejemplo simple es cuando doX() y doZ() realizar el registro y doY() hace el trabajo real:

std::cout << "Task in progress... "; // this is doX()
doTheTask();                         // this is doY()
std::cout << " ...done.\n";          // this is doZ()

En lugar de dejar que los clientes llamen a doTheTask directamente, los obligamos a pasar por ese código para asegurarnos de que se ejecuta el registro.

Y para realizar el registro de cualquier tipo de tarea, hacemos doTheTask() personalizable.

Puntos de personalización

¿Cómo? Usando polimorfismo. En el libro GoF, los autores sugieren usar polimorfismo en tiempo de ejecución, con funciones de herencia y miembros virtuales.

En nuestro ejemplo, tendríamos una clase base que se vería así:

class Task
{
public:
    void run();
    virtual ~Task() = 0;
private:
    virtual void doTheTask() const = 0;
};

Es el run no virtual función miembro que contiene el patrón de diseño del método de plantilla:

void Task::run()
{
    std::cout << "Task in progress... ";
    doTheTask();
    std::cout << " ...done.\n";
}

Una tarea dada tiene su propia clase, que implementa el Task clase base:

class MyTask : public Task
{
private:
    void doTheTask() const override;
};

(En caso de que se lo pregunte, podemos anular los métodos virtuales privados de la clase base).

Ahora, si tiene un código que usa el Task interfaz, tiene la garantía de que el registro se ejecutará sin ningún código adicional de las clases que implementan las tareas concretas.

NVI

Mientras hablamos de esto, tenga en cuenta que el patrón de diseño del método de plantilla es una forma de implementar el patrón de interfaz no virtual o NVI.

NVI consiste en exponer solo métodos no virtuales en la sección pública de una clase base. Esos métodos en sí mismos llaman métodos virtuales privados, que se implementan en clases derivadas.

El patrón NVI reconoce que la interfaz pública de una clase base no debe estar acoplada a la implementación de los métodos virtuales. De hecho, el primero representa la interfaz y el segundo representa algunos pasos de implementación.

Algunos desarrolladores llegan a nunca definir un método miembro public y virtual al mismo tiempo. Dicho de otra manera, usan NVI todo el tiempo.

NVI es una forma de implementar el patrón de diseño del método de plantilla. Cuando el método no virtual público de NVI simplemente llama al método virtual privado, sin ningún tratamiento adicional, esto puede verse como un caso degenerado de método de plantilla.

Método de plantilla con plantillas de C++

La herencia y las tablas virtuales son solo una forma de implementar el polimorfismo.

También podemos usar plantillas de C++ para implementar polimorfismo. Las plantillas crean otro tipo de polimorfismo, que se resuelve en tiempo de compilación. Este es otro tema, por lo que si la oración anterior no tiene mucho sentido, no hay problema. Volveremos a esto en otra publicación.

Una implementación del patrón de diseño del método de plantilla con plantillas de C++ se vería así:

template <typename Task>
void runTask(Task const& task)
{
    std::cout << "Task in progress... ";
    task.doTheTask();
    std::cout << " ...done.\n"; 
}

En este caso, ya no existe una clase base. Podemos pasar MyTask , que solía ser la clase derivada en nuestro ejemplo anterior, al runTask función de plantilla:

auto myTask = MyTask{};
runTask(myTask);

Ahora doTheTask la función miembro debe ser pública:

class MyTask : public Task
{
public:
    void doTheTask() const override;
};

De hecho, en la implementación anterior que usaba herencia, el código que usaba el patrón de diseño Template Method (la clase base) tenía acceso a la implementación de la tarea a través de la redirección de funciones miembro virtuales.

Ahora que el código que usa el patrón de diseño del método de plantilla está en una función libre, tiene que llamar a la interfaz pública de MyTask , por lo que su método debe ser público (a menos que runTask se hace una clase amiga).

Principio de diseño

¿Con cuál de los principios de diseño GRASP se relaciona más el patrón de diseño del método de plantilla?

Esta es una pregunta abierta, que puedes responder en la sección de comentarios.

Yo diría que se relaciona más con el polimorfismo . De hecho, la esencia del patrón de diseño del método de plantilla es tener una parte de una pieza de código que se puede ejecutar de diferentes maneras, que pueden variar independientemente de esa pieza de código. Esto es exactamente lo que permite hacer el polimorfismo.

Tenga en cuenta que el método de plantilla parece una extensión del patrón de diseño de estrategia, porque el punto de personalización del código del método de plantilla implementa un patrón de estrategia. El código que utiliza el método de plantilla puede tener varios puntos de personalización. Cada uno sería un patrón de estrategia.

Tenga en cuenta que también clasificamos la estrategia como relacionada con el polimorfismo.

¿Qué opinas?

Inicialmente, tenía la intención de cubrir en esta publicación Método de plantilla y Visitante, los dos últimos llamados "patrones de diseño de comportamiento".

Pero como ya hemos dedicado un poco de tiempo a profundizar en los detalles del método de plantilla para comprenderlo mejor, y dado que Visitor también tiene algunos aspectos de diseño que examinar, dejaremos Visitor para la próxima vez.

En resumen de esta publicación, método de plantilla es una forma de lograr el principio GRASP Polimorfismo .

¿O es eso? Si está de acuerdo o en desacuerdo, deje su opinión en la discusión a continuación.