Wednesday, February 9, 2011

Behavioral Patterns - Template Method

Definition

Provide an abstract definition for a method or a class and redefine its behavior later or on the fly without changing its structure. 

Intent

  • Define the skeleton of an algorithm in an operation, deferring some steps to client subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
  • Base class declares algorithm ‘placeholders’, and derived classes implement the placeholders.

Structure


Template Method scheme

The implementation of template_method() is: call step_one(), call step_two(), and call step_three(). step_two() is a “hook” method – a placeholder. It is declared in the base class, and then defined in derived classes. Frameworks (large scale reuse infrastructures) use Template Method a lot. All reusable code is defined in the framework’s base classes, and then clients of the framework are free to define customizations by creating derived classes as needed.

Template Method scheme

Problem

Two different components have significant similarities, but demonstrate no reuse of common interface or implementation. If a change common to both components becomes necessary, duplicate effort must be expended.



Where to use & benefits

  • To make many similar operations template.
  • From many specialized operations to a generalized operation.
  • Refactor common behavior to simplify code.
  • Algorithm related improvement.
  • Need good coding skill to conquer it.
  • May be hard to read for novice.
  • Easy to produce ambiguity if not written well.
  • Related patterns include
    • Factory Method, which is combined with template method.
    • Strategy, which is used to delegate or coordinate the template method. 



    Motivation

    If we take a look at the dictionary definition of a template we can see that a template is a preset format, used as a starting point for a particular application so that the format does not have to be recreated each time it is used.
    On the same idea is the template method is based. A template method defines an algorithm in a base class using abstract operations that subclasses override to provide concrete behavior.

    Intent

    - Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.
    - Template Method lets subclasses redefine certain steps of an algorithm without letting them to change the algorithm's structure.
    Class diagram for the classic implementation of the Template 
Method pattern (Template Method  design pattern).

    Implementation

    AbstractClass - defines abstract primitive operations that concrete subclasses define to implement steps of an algorithm.
    - implements a template method which defines the skeleton of an algorithm. The template method calls primitive operations as well as operations defined in AbstractClass or those of other objects.
    ConcreteClass - implements the primitive operations to carry out subclass-specific steps of the algorithm.
    When a concrete class is called the template method code will be executed from the base class while for each method used inside the template method will be called the implementation from the derived class.


    Examples

    For example, a loan application process may take several steps to finish. Let's assume the steps are as follows:
  • check client's bank balance history
  • check client's credit score from three different companies
  • check client's other loan information
  • check client's stock holding value
  • check client's income potential in the future
  • etc.
You may use a template method to hold the process steps together without considering the real implementation in the subclass.
abstract class CheckBackground {
  
    public abstract void checkBank();
    public abstract void checkCredit();
    public abstract void checkLoan();
    public abstract void checkStock();
    public abstract void checkIncome();

  //work as template method
    public void check() {
        checkBank();
        checkCredit();
        checkLoan();
        checkStock();
        checkIncome(); 
    }
}

class LoanApp extends CheckBackground {
    private String name;
   
    public LoanApp(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }

    public void checkBank() {
        //ck acct, balance
        System.out.println("check bank...");
    }

    public void checkCredit() {
        //ck score from 3 companies
        System.out.println("check credit...");
    }

    public void checkLoan() {
        //ck other loan info
        System.out.println("check other loan...");
    }

    public void checkStock() {
        //ck how many stock values
        System.out.println("check stock values...");
    }

    public void checkIncome() {
        //ck how much a family make
        System.out.println("check family income...");
    }
   
    //other methods
}

class TestTemplate {
    public static void main(String[] args) {
        
        LoanApp mortgageClient = new LoanApp("Judy");
        System.out.println("\nCheck client " + mortgageClient.getName()+ " Mortgage loan application. ");
        mortgageClient.check();
        
        LoanApp equityloanClient = new LoanApp("Mark");
        System.out.println("\nCheck client " + equityloanClient.getName()+ " equity loan application. ");
        equityloanClient.check();
    }
}
 C:\ Command Prompt

C:\> javac TestTemplate.java
C:\> java TestTemplate

Check client Judy Mortgage loan application.
check bank...
check credit...
check other loan...
check stock values...
check family income...

Check client Mark equity loan application.
check bank...
check credit...
check other loan...
check stock values...
check family income...

C:\>

Method overloading and method overriding are good examples of template method pattern. For example,
  • Coercion polymorphism -- refers to a single operation serving several types through implicit type conversion.
  • Overloading polymorphism -- refers to using a single identifier for different operations.
  • Parametric polymorphism -- refers to a class declaration that allows the same field names and method signatures to associate with a different type in each instance of that class.
The add() in the following code example is a template method. It can take any numerical primitive types and the result can be casted to the type you want.
//coercion polymorphism
abstract class Add {
   public abstract double add(double d1, double d2);//template
}
class AddAnyTypeNumber extends Add{
    public double add(double d1, double d2) {
        return d1 + d2;
    }
}
class Test {
   public static void main(String[] args) {
       double d1 = 10.5, d2 = 9.5;
       float f1 = 11.5f, f2 = 12.5f;
       long l1 = 1, l2 = 2;
       int i1 = 3, i2 = 4;
       short s1 = 7, s2 = 8;
       byte b1 = 5, b2 = 6;
       
       AddAnyTypeNumber addNumber = new AddAnyTypeNumber();
       
       System.out.println(addNumber.add(d1,d2));
       System.out.println((float)addNumber.add(f1,f2));
       System.out.println((long)addNumber.add(l1,l2));
       System.out.println((int)addNumber.add(i1,i2));
       System.out.println((short)addNumber.add(s1,s2));
       System.out.println((byte)addNumber.add(b1,b2));
   }
}
 C:\ Command Prompt

C:\> java Test
20.0
24.0
3
7
15
11
Note that the side effect of using coercion polymorphism is casting in and casting out if you need specific type to do the work. If you forget to do so, you may have unexpected result and it is hard to debug.
If you don't have template method pattern concept or don't know Java type promotion technique, you may write code in the following way:
abstract class Add {
   public abstract double add(double d1, double d2);
   public abstract float add(float d1, float d2);
   public abstract long add(long d1, long d2);
   public abstract int add(int d1, int d2);
   public abstract short add(short d1, short d2);
   public abstract byte add(byte d1, byte d2);
}
class AddNumber extends Add{
    public double add(double d1, double d2) {
        return d1 + d2;
    }
    public float add(float f1, float f2) {
        return f1 + f2;
    }
    public long add(long l1, long l2) {
        return l1 + l2;
    }
    public int add(int i1, int i2) {
        return i1 + i2;
    }
    public short add(short s1, short s2) {
        return (short)(s1 + s2);
    }
    public byte add(byte b1, byte b2) {
        return (byte)(b1 + b2);
    }
}
class Test {
   public static void main(String[] args) {
       double d1 = 10.5, d2 = 9.5;
       float f1 = 11.5f, f2 = 12.5f;
       long l1 = 1, l2 = 2;
       int i1 = 3, i2 = 4;
       short s1 = 7, s2 = 8;
       byte b1 = 5, b2 = 6;
       
       AddNumber addNumber = new AddNumber();
       
       System.out.println(addNumber.add(d1,d2));
       System.out.println(addNumber.add(f1,f2));
       System.out.println(addNumber.add(l1,l2));
       System.out.println(addNumber.add(i1,i2));
       System.out.println(addNumber.add(s1,s2));
       System.out.println(addNumber.add(b1,b2));
   }
}
 C:\ Command Prompt

C:\> java Test
20.0
24.0
3
7
15
11
Without using template method pattern, you may write more lines of code. The good thing is that you don't have any side effect by using specific designed method and you don't need to cast in or out.


Before

The SortUp and SortDown classes are almost identical. There is a massive opportunity for reuse - if - the embedded comparison could be refactored.
class SortUp
{
    // Shell sort
  public:
    void sort(int v[], int n)
    {
        for (int g = n / 2; g > 0; g /= 2)
          for (int i = g; i < n; i++)
            for (int j = i - g; j >= 0; j -= g)
              if (v[j] > v[j + g])
                doSwap(v[j], v[j + g]);
    }
  private:
    void doSwap(int &a, int &b)
    {
        int t = a;
        a = b;
        b = t;
    }
};

class SortDown
{
  public:
    void sort(int v[], int n)
    {
        for (int g = n / 2; g > 0; g /= 2)
          for (int i = g; i < n; i++)
            for (int j = i - g; j >= 0; j -= g)
              if (v[j] < v[j + g])
                doSwap(v[j], v[j + g]);
    }
  private:
    void doSwap(int &a, int &b)
    {
        int t = a;
        a = b;
        b = t;
    }
};

int main()
{
  const int NUM = 10;
  int array[NUM];
  srand((unsigned)time(0));
  for (int i = 0; i < NUM; i++)
  {
    array[i] = rand() % 10+1;
    cout << array[i] << ' ';
  }
  cout << '\n';

  SortUp upObj;
  upObj.sort(array, NUM);
  for (int u = 0; u < NUM; u++)
    cout << array[u] << ' ';
  cout << '\n';

  SortDown downObj;
  downObj.sort(array, NUM);
  for (int d = 0; d < NUM; d++)
    cout << array[d] << ' ';
  cout << '\n';
  system("pause");
}
3 10 5 5 5 4 2 1 5 9 1 2 3 4 5 5 5 5 9 10 10 9 5 5 5 5 4 3 2 1

After

The common implementation has been moved to an abstract base class, and a “placeholder” has been defined to encapsulate the embedded comparison.
All that remains for the SortUp and SortDown classes is to implement that placeholder.
class AbstractSort
{
    // Shell sort
  public:
    void sort(int v[], int n)
    {
        for (int g = n / 2; g > 0; g /= 2)
          for (int i = g; i < n; i++)
            for (int j = i - g; j >= 0; j -= g)
              if (needSwap(v[j], v[j + g]))
                doSwap(v[j], v[j + g]);
    }
  private:
    virtual int needSwap(int, int) = 0;
    void doSwap(int &a, int &b)
    {
        int t = a;
        a = b;
        b = t;
    }
};

class SortUp: public AbstractSort
{
    /* virtual */
    int needSwap(int a, int b)
    {
        return (a > b);
    }
};
class SortDown: public AbstractSort
{
    /* virtual */
    int needSwap(int a, int b)
    {
        return (a < b);
    }
};

int main()
{
  const int NUM = 10;
  int array[NUM];
  srand((unsigned)time(0));
  for (int i = 0; i < NUM; i++)
  {
    array[i] = rand() % 10+1;
    cout << array[i] << ' ';
  }
  cout << '\n';

  AbstractSort *sortObjects[] = 
  {
    new SortUp, new SortDown
  };
  sortObjects[0]->sort(array, NUM);
  for (int u = 0; u < NUM; u++)
    cout << array[u] << ' ';
  cout << '\n';

  sortObjects[1]->sort(array, NUM);
  for (int d = 0; d < NUM; d++)
    cout << array[d] << ' ';
  cout << '\n';
  system("pause");
}
1 6 6 2 10 9 4 10 6 4 1 2 4 4 6 6 6 9 10 10 10 10 9 6 6 6 4 4 2 1

No comments:

Post a Comment