Patrones de diseño. Template Method

Introducción

El patrón de diseño Template Method se utiliza en situaciones en las cuales existe un determinado esqueleto que conforma pasos comunes dentro de un algoritmo o procedimiento en una clase.

Una analogía podría ser la forma en que resolvemos un problema con distintos expertos, por ejemplo, supongamos el caso del ciclo de vida del software (simplificado), que podemos estructurar en los siguientes pasos:

  • Preparar un documento de concepto
  • Elaborar la especificación de requisitos
  • Elaborar el diseño de la aplicación
  • Codificar la solución
  • Realizar las pruebas unitarias
  • Realizar las pruebas de sistema

Ahora bien, el proceso es el mismo cada vez. El documento de concepto puede realizarlo comercial o marqueting, elaborarlo en Word o en PDF. El formato del documento de requisitos puede cambiar o usar un programa de apoyo como DOORS y el código puede realizarse utilizando Delphi, C# o Java, pero el proceso general permanece el mismo, solo cambian los expertos que se encargan de cada cosa.

Problema

Este patrón de diseño afronta el problema de la existencia de pasos dentro de un algoritmo, siendo dichos pasos comúnes a cualquier implementación, que pueden (y queremos que puedan) ser implementados sin embargo de distintas formas, delegando la implementación de dichos pasos en metodos proporcionados por otras clases (generalmente, pero no imprescindiblemente, hijas).

Estructura

Template.png

Código de ejemplo

Vamos a ver un pequeño código de ejemplo muy sencillo. El ejemplo consiste en un caso en el que obtenemos un string de texto, lo transformamos en mayusculas, lo invertimos y obtenemos su hash.

Para nuestro ejemplo existen una serie de pasos comunes, obtener el texto, pasarlo a mayusculas, inventirlo y finalmente obtener su hash. Sin embargo existen distintas formas de obtener el hash de una función y varias fuentes de las que obtener el string, sin embargo los otros dos pasos son comunes:

interface
type TTemplateDemo = class
  public
    function ObtenerString : string; virtual; abstract;
    function ObtenerHash(entrada : string) : integer; virtual; abstract;
    function Procesar : integer;
end;

type TTemplateImp1 = class(TTemplateDemo)
  public
    function ObtenerString : string; override;
    function ObtenerHash(entrada : string) : integer; override;
end;

type TTemplateImp2 = class(TTemplateDemo)
  public
    function ObtenerString : string; override;
    function ObtenerHash(entrada : string) : integer; override;
end;

implementation

{ TTemplateDemo }
function TTemplateDemo.Procesar : integer;
var
  str : string;
begin
  str = ObtenerString;
  str = PasarAMayusculas(str);
  str = Invertir(str);
  result := ObtenerHash(str);
end;

{ TTemplateImp1 }
function TTemplateImp1.ObtenerString : string
begin
  // Aquí iría el código para obtener
  // el string preguntandole al usuario
end;

function TTemplateImp1.ObtenerHash(entrada : string);
begin
  // Aquí iría el código para obtener
  // el hash con un Elf Hash
end;

{ TTemplateImp2 }
function TTemplateImp2.ObtenerString : string
begin
  // Aquí iría el código para obtener
  // el string desde un archivo
end;

function TTemplateImp2.ObtenerHash(entrada : string);
begin
  // Aquí iría el código para obtener
  // el hash con una función hash básica
end;

Evidentemente el código es muy sencillo pero se observa el funcionamiento. El algoritmo está implementado en la clase padre mientras que los distintos hijos implementan las funcionalidades delegadas heredando el algoritmo en si.

Ejemplos reales

Este patrón puede encontrarse en numerosos sitios, es una de esas cosas que tienden a usarse incluso sin saber que se está usando un patrón conocido y que cae, por derecho propio, dentro de los usos más comunes de la herencia y los metodos abstractos.

Dibujo de objetos

Un lugar idoneo de aplicación entra dentro del dibujo 3D, por ejemplo en un motor gráfico. En este caso podemos tener una serie de objetos que son renderizables, es decir, que pueden sacarse en pantalla. Cada objeto tiene una serie de pasos comunes, por ejemplo situarlo en pantalla, ajustar su rotación, y otros que siendo comunes en cuanto a que se deben hacer (dibujar el objeto propiamente dicho, calcular las sombras que proyecta, aplicar las textruas) pueden variar enormemente de un objeto a otro.

En este caso podemos definir un objeto padre llamado simplemente BaseObject cuyo metodo render contenga llamadas a metodos abstractos tales como RenderizarObj, AplicarTexturas, etc.

Plugins

Otro uso particular se da en la creación de sistemas de plugins. Por definición este patrón garantiza la existencia de distintos metodos en una clase que pueden ser "sobreescritos" por un descendiente, es decir, distintas partes de un algoritmo que esperamos que otro proporcione.

En un sistema de plugins existen unos hooks o enganches de un determinado algoritmo (por ejemplo la parte que muestra un texto en pantalla) que esperamos que sean (o queremos que puedan ser) interceptadas por un plugin que implemente o modifique dicha funcionalidad.

7.25
Average: 7.3 (4 votes)
Your rating: None