Qué es el patron de diseño Template Method

Qué es el patron de diseño Template Method

Siguiendo nuestro curso de programación hoy veremos el patrón Template Method. El patrón de comportamiento Template Method define el esqueleto de un algoritmo en una operación, defiriendo algunos pasos hasta las subclases. Permite que éstas redefinan ciertos pasos del algoritmo sin cambiar la estructura del algoritmo en sí.

uml_ejemplo_template_method

Aplicaciones

Utilícese el patrón Template Method cuando:

  • Se quiera implementar las partes de un algoritmo que no cambian y dejar que las subclases implementan aquellas otras que puedan variar.
  • Por motivo de factorizar código, cuando movemos cierto código a una clase base común evitar código duplicado.
  • Para controlar el modo en que las subclases extienden la clase base. Haciendo que solo sea a través de unos métodos de plantilla datos.

Ejemplo

Sea un framework de aplicaciones que proporcionas las clases Application y Document. La primera es la responsable de abrir los documentos y almacenados en ficheros. La segunda representa la información en si del documento, una vez que ya ha sido leído el fichero. Las aplicaciones construidas con el framework redefinirán ambas clases para adaptarlas a sus necesidades concretas. ¿Cómo haríamos un método genérico openDocument de la clase Application?

uml_ejemplo_template_method

Estructura general del patrón

esquema_template_method

  • AbstractClass (Application): define las operaciones primitivas abstractas que redefinirán las subclases. También implementa un método de plantilla con el esqueleto del algoritmo.
  • ConcreteClass (MyApplication): implementa las operaciones primitivas.

Consecuencias del uso del patrón

El uso de este patrón genera una serie de consecuencias que tenemos que tener presente:

  • La principal: los métodos de plantilla sirven para la reutilización de código.
  • Inversión de control: es la clase padre quien llama a las operaciones de los hijos.
  • Los métodos de plantilla pueden llamar a los siguientes tipos de operaciones: Operaciones concretas de las subclases o de otras clases, operaciones concretas en la propia clase base abstracta, operaciones primitivas (es decir, abstractas), métodos de fabricación (o también llamados factorías) y también operaciones de enganche (hook).

Las operaciones de enganche proporcionan comportamiento predeterminado que las subclases pueden redefinir si es necesario. Normalmente, la implementación predeterminada no hace nada.

Para ilustrar este patrón vamos a partir de un código inicial al que vamos a aplicarle este patrón.

Código inicial

enum Platform {    ANDROID, WINDOWS, PLAYSTATION};public class BallGame {    // Seleccionar para qué plataforma se quiere generar el juego    private Platform platform = Platform.ANDROID;    // private Platform platform = Platform.WINDOWS;    // private Platform platform = Platform.PLAYSTATION;    private AndroidAPI android;    private WindowsAPI windows;    private Playstation5API playstation;    public void play() {        // Inicializar la API adecuada        setAPI();        Image2D image = loadImage("Bola.jpg");        // Lógica principal del juego        for (int i = 0; i < 10; i++) {            Point point = getPosition();            drawBall(image, point);        }    }    private void setAPI() {        if (platform == Platform.ANDROID)            android = new AndroidAPI();        else if (platform == Platform.WINDOWS)            windows = new WindowsAPI();        else            playstation = new Playstation5API();    }    private Image2D loadImage(String file) {            Image2D image;            if (platform == Platform.ANDROID)                    image = android.loadResource(file);            else if (platform == Platform.WINDOWS)                    image = windows.loadFile(file);            else                    image = playstation.loadGraphics(file);            return image;    }    private Point getPosition() {            Point point;            if (platform == Platform.ANDROID)                    point = android.getTouch();            else if (platform == Platform.WINDOWS)                    point = windows.getMouseClick();            else                    point = playstation.getJoystick();            return point;    }    private void drawBall(Image2D image, Point point) {            if (platform == Platform.ANDROID)                    android.draw(point.x, point.y, image);            else if (platform == Platform.WINDOWS)                    windows.paint(point.x, point.y, image);            else                    playstation.render(point.x, point.y, image);    }}

Si nos fijamos esta clase lo que hace es simular el comportamiento de un juego. Dependiendo de que plataforma sea las acciones sobre el juego se realizarán de una forma diferente. Es importante tener una cosa clara, las clases AndroidAPI, WindowsAPI y PlayStation5API se supone que son proporcionados por un tercero, a las cuales no tenemos acceso, no podemos modificarlas ni nada simplemente utilizarlas en nuestro código.

Volviendo al código, ¡Esto está fatal! Es necesario que se puedan elegir las plataformas de forma dinámica no teniendo que modificar el código. Pensemos en aplicar el patrón. Lo primero que tenemos que definir es la clase plantilla, una clase abstracta con la funcionalidad básica del juego y unos métodos que tendrán que ser redefinidos por unas clases concreta.

public abstract class BallGameTemplateMethod {    public void play(){        Image2D image = load("Bola.jpg");        for (int i = 0; i < 10; i++) {            Point point = getClick();            paint(point.x, point.y, image);        }    }    public abstract Image2D load(String name);    public abstract void paint(int x, int y, Image2D image);    public abstract Point getClick();}

Por ejemplo, para la API de Android sería una clase similar a:

public class BallGameAndroid extends BallGameTemplateMethod {    private AndroidAPI e = new AndroidAPI();    public Image2D load(String name) {        return e.loadResource(name);    }    public void paint(int x, int y, Image2D image) {        e.draw(x, y, image);    }    public Point getClick() {        return e.getTouch();    }}

Si nos fijamos es esta clase la que utilizará la clase concreta de la API, en este caso de AndroidAPI que será instanciada en una especie de decorador.

¿Quién sería capaz de hacer esta refactorización de código empleando otro patrón? ¿Qué pasaría si tenemos otro tipo de juego? En el próximo artículo contestaremos a estas cuestiones, mientras tanto podéis intentar buscar la solución.

Para ti
Queremos saber tu opinión. ¡Comenta!