Home > GOF Pattern, java > GoF Patterns: Template Method

GoF Patterns: Template Method

7 agosto 2012

Translate in English with Google Translate
In questo articolo tratterò il pattern Template

Motivazione
Si tratta di un pattern comportamentale basato su classi e viene utilizzato per definire la struttura di un algoritmo delegando alcuni passi di dettaglio alle sottoclassi.
Questo pattern nasce dall’esigenza di specificare l’ordine delle operazioni da effettuare ma di delegare alle sottoclassi l’implementazione di alcune operazioni. Pertanto il metodo che definisce l’algoritmo viene implementato nella superclasse mentre i metodi che definiscono i comportamenti di dettaglio vengono dichiarati astratti nella superclasse ed implementati nelle sottoclassi.

L’utilizzo di questo pattern permette di:

  • implementare una sola volta la parte “immutata” dell’algoritmo e di consentire alle sottoclassi di implementare il comportamento delle parti “variabili”.
  • individuare comportamenti comuni delle sottoclassi e “promuoverli” a comportamenti della superclasse in modo da evitare la duplicazione di codice: un esempio di refactoring di codice.
  • individuare comportamenti NON comuni delle sottoclassi e definire un metodo di gancio hook per consentire alle sottoclassi di implementare uno specifico comportamento.

L’utilizzo di questo pattern si presenta con una serie di varianzioni anche se per una completa aderenza alle intenzioni occorrerebbe che rispettasse alcune caratteristiche:

  • La superclasse venga dichiarata abstract in modo da non poter essere instanziata dal Client.
  • il metodo template venga dichiarato final in modo tale che le sottoclassi non siano in grado di modificarlo e cambiare il suo comportamento.
  • i metodi primitivi vengano dichiarati abstract nella superclasse e vengano implementati nelle sottoclassi.
  • le sottoclassi non invochino direttamente il metodo concreto della superclasse ma lascino che sia la superclasse ad invocarli all’occorrenza, Principio di Hollywood: “non chiamarci, ti chiameremo noi”
  • minimizzare il numero di metodi primitivi per evitare che lo sviluppatore delle sottoclassi debba implementare troppi metodi per poter usare questo pattern.
  • creare un metodo concreto hook vuoto tra le fasi dell’algoritmo per permettere agli sviluppatori di sovrascriverlo ed implementare un passaggio logico da loro richiesto.

Partecipanti e Struttura
Questo pattern è composto dai seguenti partecipanti:

  • AbstractClass: definisce il metodo concreto ed i metodi primitivi astratti. Il metodo concreto richiama i metodi primitivi implementati nelle sottoclassi.
  • ConcreteClass: implementa i metodi primitivi per svolgere i passi specifici dell’algoritmo ed eventualmente i metodi hook.

Vediamo come si presenta il Pattern Factory Method utilizzando il Class Diagram in UML:

Template Pattern

Template Pattern

Conseguenze
Tale pattern presenta i seguenti vantaggi/svantaggi:

  • definizione di metodi comuni: occorre comprendere quali sono i comportamenti comunementi implementati dalle sottoclassi e “promuoverli” come comportamenti della superclasse in modo da centralizzarli ed evitare la duplicazione del codice.
  • definizione dei metodi hook: occorre permettere alle sottoclassi di poter definire un comportamento durante l’esecuzione dell’algoritmo tramite l’introduzione di un metodo “hook” che POSSONO ma NON SONO OBBLIGATE a ridefinire.

Implementazione
L’utilizzo di questo pattern lo troviamo nel JDK, per esempio in java.util.AbstractList, ma anche nella libreria JEE, per esempio in javax.servlet.http.HttpServlet.
Mi sono proposto di vedere l’utilizzo di questo pattern:

  • nelle librerie di Tomcat in merito all’implementazione delle Servlet
  • tramite un esempio di generazione di documenti in formati diversi

Implementazione delle Servlet
Esaminiamo javax.servlet.http.HttpServlet e vediamo in dettaglio come è stata implementata su Tomcat.
Ma prima vediamo un Class Diagram di esempio che ci mostri i metodi di nostro interesse di HttpServlet ed una nostra Servlet MyHttpServlet che implementa i metodi primitivi.

Template Pattern Servlet

Template Pattern Servlet

La classe HttpServlet è abstract pertanto richiede di essere implementata. Il metodo template di riferimento che gestisce l’algoritmo è service(). In particolare troviamo il metodo public void service() che referenzia il metodo protected void service() in cui viene elaborato l’algoritmo ed in base al metodo HTTP invocato (GET, PUT, HEAD, POST, TRACE, DELETE, OPTIONS) invocherà il metodo primitivo presente nella propria classe o nella Servlet instanziata MyHttpServlet.

package javax.servlet.http;

public abstract class HttpServlet extends GenericServlet implements java.io.Serializable {

  //... codice

  public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
     //... codice
     service(request, response);
  }

   protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      String method = req.getMethod();

      if (method.equals(METHOD_GET)) {
        //...codice
        doGet(req, resp);
      } else if (method.equals(METHOD_HEAD)) {
        doHead(req, resp);
      } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);
      } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);
      } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);
      } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req,resp);
      } else if (method.equals(METHOD_TRACE)) {
        doTrace(req,resp);
      } else {
        //...codice
        String errMsg = lStrings.getString("http.method_not_implemented");
        //...codice
        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
      }
   }
   //... codice
}

La servlet MyHttpServlet che eredita dalla classe astratta HttpServlet effettuerà l’overwrite dei metodi primitivi di interesse che saranno richiamati dal metodo service() di HttpServlet. Lo sviluppatore della servlet MyHttpServlet potrà anche effettuare l’overwrite del metodo service() in quanto questi non è stato dichiarato final ed in questo modo potrà personalizzare l’algoritmo, ma ciò è sconsigliato da Sun/Oracle che invece promuove l’overwrite dei singoli metodi primitivi (doGet(), doPost() ecc…), come ho fatto di seguito:

package patterns.template.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyHttpServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //codice implementativo
    }

    @Override
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //codice implementativo
    }
}

Generazione di un documento in formati diversi
Adesso realizziamo una mini applicazione che ci consenta di generare dei documenti con formati diversi. L’algoritmo di creazione del documento è definito nella superclasse e stabilisce una serie di passi da realizzare ed obbliga le sottoclassi ad implementare i metodi primitivi abstract e dà la facoltà di implementare i metodi hook.
Immaginiamo di voler creare un documento tabellare in cui vengono elencati i films su DVD disponibili in diversi formati: XML, CSV ed HTML.

Vediamo come si presenta l’implementazione del pattern in UML in base all’esempio:

Template Pattern - Esempio Documento

Template Pattern – Esempio Documento

Realizziamo la superclasse che contiene il metodo template createTable() che definisce:

  • i passi di elaborazione dell’algoritmo, identificati dalla sequenza di metodi da invocare
  • i metodi private dell’algoritmo che non devono essere modificati: logging() e saveFile()
  • i metodi primitivi abstract che devono essere implementati dalle sottoclassi: doBody()
  • i metodi hook che, a scelta, possono essere implementati dalle sottoclassi: doHook()
package patterns.template.document;

import java.io.FileOutputStream;
import java.util.List;

public abstract class Table {

    protected StringBuilder tableContent;
    protected String filePath = "/testPattern/table";

    //metodo template "public final" che DEVE essere visibile
    //MA NON DEVE essere sovrascritto in quanto definisce l'algoritmo
    public final void createTable(List dvdList) {
        logging();
        tableContent = doBody(dvdList);
        doHook();
        saveFile();
    }

    //metodo fattorizzato "private" facente parte dell'algoritmo
    private void logging() {
        System.out.println("## Generazione della tabella in base al formato/contenuto richiesto ##");
    }

    //metodo "abstract" primitivo che DEVE essere implementato
     protected abstract StringBuilder doBody(List dvdList);

    //metodo concreto vuoto che PUO' ESSERE implementato
    protected void doHook() {
    }

    //metodo fattorizzato "private" facente parte dell'algoritmo
    private void saveFile() {
        try {
            new FileOutputStream( filePath ).write(tableContent.toString().getBytes());
            System.out.println("## File path: " + filePath );
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

Creiamo la prima sottoclasse che implementa il metodo abstract doBody() e definisce il comportamento specifico dell’algoritmo relativamente alla sezione che si occupa di creare la tabella in base al formato CSV.
L’implementazione del metodo doHook() è utilizzata per cambiare il formato del file in “.csv”

package patterns.template.document;

import java.util.List;

public class CSVTable extends Table {

    @Override
    protected StringBuilder doBody(List<DVD> dvdList) {
        StringBuilder csv = new StringBuilder();
        csv.append("TITOLO;REGIA;GENERE" + "\n");
        for (DVD dvd : dvdList) {
            csv.append(dvd.getTitolo() + ";");
            csv.append(dvd.getRegia() + ";");
            csv.append(dvd.getGenere());
            csv.append("\n");
        }
        return csv;
    }

    @Override
    protected void doHook() {
        this.filePath += ".csv";
    }
}

Adesso realizziamo la seconda sottoclasse che implementa il metodo doBody() creando una tabella HTML e definisce il comportamento del metodo doHook() per aggiungere un link alla tabella.

package patterns.template.document;

import java.util.List;

public class HTMLTable extends Table {

    @Override
    public StringBuilder doBody(List<DVD> dvdList) {
        StringBuilder html = new StringBuilder();
        html.append("<TABLE border=1>" + "\n");
        html.append(" <TR>" + "\n");
        html.append("  <TD>TITOLO</TD>" + "\n");
        html.append("  <TD>REGIA</TD>" + "\n");
        html.append("  <TD>GENERE</TD>" + "\n");
        html.append(" </TR>" + "\n");

        for (DVD dvd : dvdList) {
            html.append(" <TR>" + "\n");
            html.append("  <TD>" + dvd.getTitolo() + "</TD>" + "\n");
            html.append("  <TD>" + dvd.getRegia() + "</TD>" + "\n");
            html.append("  <TD>" + dvd.getGenere() + "</TD>" + "\n");
            html.append(" </TR>" + "\n");
        }
        html.append("</TABLE>" + "\n");
        return html;
    }

    @Override
    protected void doHook() {
        this.tableContent.append("<A HREF=\"javascript:alert('Films di prima visione')\">Info</A>");
        this.filePath = "/testPattern/tabella.html";
    }
}

Realizziamo la terza sottoclasse che crea un documento XML e modifica l’estensione del file in .xml

package patterns.template.document;

import java.util.List;

public class XMLTable extends Table{

    @Override
    public StringBuilder doBody(List<DVD> dvdList) {
        StringBuilder xml = new StringBuilder();
        xml.append("<DVD_LIST>" + "\n");
        for(DVD dvd: dvdList){
            xml.append(" <DVD>" + "\n");
            xml.append("  <TITOLO>" + dvd.getTitolo() + "</TITOLO>" + "\n");
            xml.append("  <REGIA>" + dvd.getRegia() + "</REGIA>" + "\n");
            xml.append("  <GENERE>" + dvd.getGenere() + "</GENERE>" + "\n");
            xml.append(" </DVD>" + "\n");
        }
        xml.append("</DVD_LIST>" + "\n");
        return xml;
    }

    @Override
    protected void doHook() {
        this.filePath += ".xml";
    }

}

Considerando che la nostra applicazione genera una lista di elementi che individuano dei films disponibili su DVD, ho creato un Bean dal nome DVD che contiene le proprietà di nostro interesse: titolo, regia e genere.

package patterns.template.document;

import java.io.Serializable;

public class DVD implements Serializable {

    private String titolo;
    private String regia;
    private String genere;

    public DVD(String titolo, String regia, String genere) {
        this.titolo = titolo;
        this.regia = regia;
        this.genere = genere;
    }

    public DVD() {}

    public String getTitolo() {
        return titolo;
    }

    public void setTitolo(String titolo) {
        this.titolo = titolo;
    }

    public String getRegia() {
        return regia;
    }

    public void setRegia(String regia) {
        this.regia = regia;
    }

    public String getGenere() {
        return genere;
    }

    public void setGenere(String genere) {
        this.genere = genere;
    }
}

Il Client che effettua l’invocazione deve semplicemente invocare la sottoclasse afferente al formato di interesse (XMLTable, CSVTable e HTMLTable) per ottenere la sua instanza, successivamente l’invocazione del metodo template createTable() della superclasse invocherà il metodo primitivo della sottoclasse.

package patterns.template.document;

import java.util.ArrayList;
import java.util.List;

public class Client {

    public static void main(String[] args) {
        Table table = null;

        //Creazione di una tabella in formato XML
        table = new XMLTable();
        table.createTable( dvdList );

        //Creazione di una tabella in formato CSV
        table = new CSVTable();
        table.createTable( dvdList );

        //Creazione di una tabella in formato HTML
        table = new HTMLTable();
        table.createTable( dvdList );

    }

    private final static List dvdList;
    static {
        dvdList = new ArrayList();
        dvdList.add(new DVD("Biancaneve e il cacciatore", "Rupert Sanders", "Fantasy"));
        dvdList.add(new DVD("La bella e la bestia", "Gary Trousdale", "Animazione"));
        dvdList.add(new DVD("Freerunner - corri o muori", "Lawrence Silverstein", "Azione"));
    }
}

L’esecuzione del Client genera 3 file: table.xml, table.csv e tabella.html, prodotti dallo stesso algoritmo ma i cui dettagli sono stati definiti dai metodi primitivi.

$JAVA_HOME/bin/java patterns.template.document.Client
## Generazione della tabella in base al formato/contenuto richiesto ##
## File path: /testPattern/table.xml
## Generazione della tabella in base al formato/contenuto richiesto ##
## File path: /testPattern/table.csv
## Generazione della tabella in base al formato/contenuto richiesto ##
## File path: /testPattern/tabella.html
Categorie:GOF Pattern, java
  1. Savio
    21 agosto 2012 alle 11:14 AM

    Ciao, sai dirmi le differenze tra il pattern Template Method ed il pattern Strategy. Anche se diversi, sembrano fare la stessa cosa, e a questo punto vorrei capire quando usare l’uno o l’altro. Grazie

    • 23 agosto 2012 alle 11:33 AM

      Il Template Method si usa quando occorre variare una parte, uno step dell’algoritmo, mentre la parte in comune viene centralizzata nella superclasse. Inoltre questo pattern sfrutta l’ereditarietà, in particolare il principio di hollywood, in cui la superclasse invoca la sottoclasse.
      Lo Strategy si usa quando occorre definire una variante dell’algoritmo che non segue gli step iniziali, inoltre non condivide una parte in comune dell’algoritmo ( sempre che non venga definita una gerarchia di ereditarietà che specializzi l’algoritmo). Inoltre questo pattern sfrutta la delega, ossia il Context instanzia l’algoritmo.
      E’ anche possibile combinare questi due pattern in modo che lo step primitivo “implementato” dalla sottoclasse del template “deleghi” una classe di strategy. In entrambi i casi le reference sono definite a run-time e non a compile-time.
      La tendenza è di favorire la composizione a fronte dell’ereditarietà ma ciò non giustifica la scelta del pattern che si applica in contesti diversi.
      Pertanto io utilizzerei il template quando voglio “imporre” un algoritmo e lasciare libertà solo per uno o pochi passi implementativi (metodo primitivo e metodo hook) mentre utilizzerei lo Strategy quando voglio lasciare la “libertà” di definire un algoritmo “nuovo” ossia che presenti degli step completamente diversi.
      Spero di esserti stato di aiuto. Ciao.
      Link:
      when-to-use ,
      what-is-the-difference ,
      Patterns-Strategy-Template-Method

  1. No trackbacks yet.
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: