Template method pattern in Java with sample code

Template Method pattern is a behavioral design pattern. It is among one of the most widely used design patterns. It defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

Sometimes you want to specify the order of operations (or steps) that a method uses, but allow subclasses to provide their own implementations of some of these operations.

Real World Examples of Template Pattern:

  • Servlet (explained after example)
  • Java – MergeSort , Arrays sort(Refer Head First Book)
  • JFrame – update() (Refer Head First Book)
  • HibernateCallback used in HibernateTemplate’s execute(..) methods
  • Struts – RequestProcessor

Case Study:
Develop an application for processing flat file for
1. Stock
2. Derivatives

Requirements:
Stock support CSV format, where as Derivative support txt. Stock data is separated by delimiter “,”, where as Derivative data within Flat file separated by “|”. Data is persisted within database. We can configure logging into system optionally.

Class Diagram:

Explanation:
DataHandler provide template method to algorithm for processing financial system data files.
Algorithm: Template Method – execute()
1. Validate file type
2. Transform Data
3. Process Data
Subclass should implement specific behaviour based on investment file type (Stock/Derivative).
Hook: Method – isDebugMode()
DataHandler define default configuration for setting log in debug mode. Subclass can override this method. Subclass can use hook to control execution of algorithm.

Code:
Template Class

package com.thecafetechno.behavioral.templateMethodPattern;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;

/**
 * DataHandler provide template method execute which define algorithm for
 * processing financial system data files.
 *
 * Algorithm: 1. Validate file type 2. Transform Data 3. Process Data
 *
 * Subclass should implement specific behaviour based on investment file type
 * (Stock/Derivative).
 *
 */

public abstract class DataHandler {
    /**
     * This is template method define algorithm for accessing financial system
     * data files.
     *
     * @param fileName
     *            String
     */

    public final void execute(String fileName) {
	if (isValidFileType(fileName)) {
	    try { // process file
		// Open the file that is the first
		FileInputStream fstream = new FileInputStream(fileName);

		// Get the object of DataInputStream
		DataInputStream in = new DataInputStream(fstream);
		BufferedReader br = new BufferedReader(
			new InputStreamReader(in));
		String strLine;

		// Read File Line By Line
		while ((strLine = br.readLine()) != null) {
		    if (isDebugMode()) {
			System.out.println("Processing data:" + strLine);
		    }
		    DataObject data = transformData(strLine);
		    process(data);
		}
		// Close the input stream
		in.close();
	    } catch (Exception e) { // Catch exception if any
		System.err.println("Error:" + e.getMessage());
	    }
	}
    }

    /**
     * Validate File type. Subclass should implement specific behaviour.
     *
     * @param fileName
     *            String
     * @return boolean
     */
    protected abstract boolean isValidFileType(String fileName);

    /**
     * Transform data to data Object. Subclass should implement specific
     * behaviour.
     *
     * @param fileName
     *            String
     * @return DataObject
     */
    protected abstract DataObject transformData(String fileName);

    /**
     * Subclass should override this method to persist/process data within
     * database.
     *
     * @param data
     *            DataObject
     */
    protected abstract void process(DataObject data);

    /**
     * This is a hook method and DataHandler define default configuration for
     * setting log in debug mode. Subclass can override this method. Subclass
     * can use hook to control execution of algorithm.
     *
     * @return boolean
     */

    protected boolean isDebugMode() {
	return false;
    }
}

Template SubClass StockHandler:


package com.thecafetechno.behavioral.templateMethodPattern;

import java.math.BigDecimal;
import java.util.StringTokenizer;

public class StockHandler extends DataHandler {
    @Override
    protected boolean isValidFileType(String fileName) {
	// Support processing of file if in CSV format
	return fileName.endsWith(".csv");
    }

    @Override
    protected void process(DataObject data) {
	if (isDebugMode()) {
	    log("Persisting Data!!!");
	}

	// Persist information
	StockDataObject dataVal = (StockDataObject) data;
	log("Stock Name:" + dataVal.getName());
	log("High Value:" + dataVal.getHighValue().toString());
	log("Low Value:" + dataVal.getLowValue().toString());
	log("Open Value:" + dataVal.getOpenValue().toString());
	log("Close Value:" + dataVal.getCloseValue().toString());
    }

    @Override
    protected DataObject transformData(String strLine) {
	if (isDebugMode()) {
	    log("\nStarted Transformation!!!");
	}
	StringTokenizer tokenizer = new StringTokenizer(strLine, ",");
	StockDataObject dataObject = null;
	if (tokenizer.hasMoreTokens() && tokenizer.countTokens() == 5) {
	    dataObject = new StockDataObject();
	    dataObject.setName(tokenizer.nextToken());
	    dataObject.setOpenValue(new BigDecimal(tokenizer.nextToken()));
	    dataObject.setHighValue(new BigDecimal(tokenizer.nextToken()));
	    dataObject.setLowValue(new BigDecimal(tokenizer.nextToken()));
	    dataObject.setCloseValue(new BigDecimal(tokenizer.nextToken()));
	}
	if (isDebugMode()) {
	    log("Transform Successful!!!");
	}
	return dataObject;
    }

    private void log(String str) {
	System.out.println(str);
    }

    @Override
    protected boolean isDebugMode() {
	return true;
    }
}

DerivativeHandler


package com.thecafetechno.behavioral.templateMethodPattern;

public class DerivativeHandler extends DataHandler {
    @Override
    protected boolean isValidFileType(String fileName) {
	// Support processing of file if in txt format
	return fileName.endsWith(".txt");
    }

    @Override
    protected void process(DataObject data) {
	System.out.println("Process data for derivative!!!");
    }

    @Override
    protected DataObject transformData(String fileName) {
	System.out.println("Transforming data for derivative!!!");
	return null;
    }
}

Reference DataObject Class


package com.thecafetechno.behavioral.templateMethodPattern;

import java.io.Serializable;

public interface DataObject extends Serializable {

}

Data Object for Stock.

package com.thecafetechno.behavioral.templateMethodPattern;

import java.math.BigDecimal;

/*
 * Data Object for Stock
 */
public class StockDataObject implements DataObject {

    private static final long serialVersionUID = 1L;
    private String name;
    private BigDecimal openValue;
    private BigDecimal highValue;
    private BigDecimal lowValue;
    private BigDecimal closeValue;

    public String getName() {
	return name;
    }

    public void setName(String name) {
	this.name = name;
    }

    public BigDecimal getOpenValue() {
	return openValue;
    }

    public void setOpenValue(BigDecimal openValue) {
	this.openValue = openValue;
    }

    public BigDecimal getHighValue() {
	return highValue;
    }

    public void setHighValue(BigDecimal highValue) {
	this.highValue = highValue;
    }

    public BigDecimal getCloseValue() {
	return closeValue;
    }

    public void setCloseValue(BigDecimal closeValue) {
	this.closeValue = closeValue;
    }

    public BigDecimal getLowValue() {
	return lowValue;
    }

    public void setLowValue(BigDecimal lowValue) {
	this.lowValue = lowValue;
    }
}

 

package com.thecafetechno.behavioral.templateMethodPattern;

import java.math.BigDecimal;

/*
 * Data Object for Stock
 */
public class StockDataObject implements DataObject {

    private static final long serialVersionUID = 1L;
    private String name;
    private BigDecimal openValue;
    private BigDecimal highValue;
    private BigDecimal lowValue;
    private BigDecimal closeValue;

    public String getName() {
	return name;
    }

    public void setName(String name) {
	this.name = name;
    }

    public BigDecimal getOpenValue() {
	return openValue;
    }

    public void setOpenValue(BigDecimal openValue) {
	this.openValue = openValue;
    }

    public BigDecimal getHighValue() {
	return highValue;
    }

    public void setHighValue(BigDecimal highValue) {
	this.highValue = highValue;
    }

    public BigDecimal getCloseValue() {
	return closeValue;
    }

    public void setCloseValue(BigDecimal closeValue) {
	this.closeValue = closeValue;
    }

    public BigDecimal getLowValue() {
	return lowValue;
    }

    public void setLowValue(BigDecimal lowValue) {
	this.lowValue = lowValue;
    }
}

Client Class:


package com.thecafetechno.behavioral.templateMethodPattern;

/*
 * Sample client to test Template Pattern.
 */
public class TemplateMethodClient {

    public static void main(String[] args) {
	DataHandler handler = new StockHandler();
	handler.execute("test.csv");
    }
}

Real World Example of Template Method Pattern: Servlet
Servlet Life Cycle
init()
service()
destroy()
Service method is an abstract method in GenericServlet Class.
HttpServlet extends GenericServlet and implement service method.
Based on HTTP request type, HTTPServlet invokes
- doGet, doPost, doHead, doPut, doDelete, doTrace, doOptions
Developers extend HttpServlet and write meaningful method – doGet, doPost
Template – HttpServlet service method define template for handling HTTP requests.

Intresting Fact about Template Method Pattern: Template Method follow
Hollywood Principle – “Don’t call me, I will call you”
Enforces “Open Closed principle” – A class should be open for extension, but closed for modification.