Thursday, 5 December 2013

Strategy pattern / Collections.sort()

algorithm's behaviour can be selected at runtime.  

the strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. 

Strategy pattern is one of the behavioral design pattern. Strategy pattern is used when we have multiple algorithm for a specific task and client decides the actual implementation to be used at runtime.
Strategy pattern is also known as Policy Pattern. We defines multiple algorithms and let client application pass the algorithm to be used as a parameter. One of the best example of this pattern is Collections.sort() method that takes Comparator parameter. Based on the different implementations of Comparator interfaces, the Objects are getting sorted in different ways



public class Student {

      private int rollNo;
      private String fname;
      private String lname;

package strategyP;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class TestStudent {

      public static void main(String[] args) {

            ArrayList<Student> studentList = new ArrayList<Student>();

            studentList.add(new Student(1, "Aditya", "Sharma"));
            studentList.add(new Student(2, "Beena", "Mishra"));
            studentList.add(new Student(3, "Priyanka", "Pandey"));
            studentList.add(new Student(4, "Vikas", "Jain"));
            studentList.add(new Student(5, "Rose", "Marlo"));

            Comparator<Student> comparasionStrategy = new Comparator<Student>() {

                  @Override
                  public int compare(Student arg0, Student arg1) {

                        return arg0.getFname().compareTo(arg1.getFname());

                  }
            };
     
            Collections.sort(studentList,comparasionStrategy);
           
            for (Student student : studentList) {
                  System.err.println(student.toString());
            }
           
            Collections.sort(studentList, new Comparator<Student>() {

                  @Override
                  public int compare(Student arg0, Student arg1) {
                        return arg1.getRollNo() - arg0.getRollNo();
                  }

            });

            for (Student student : studentList) {
                  System.out.println(student.toString());
            }

      }
}




Where Would I Use This Pattern(A More Realistic Example)

The Strategy pattern is to be used where you want to choose the algorithm to use at runtime. A good use of the Strategy pattern would be saving files in different formats, running various sorting algorithms, or file compression.
The Strategy pattern provides a way to define a family of algorithms, encapsulate each one as an object, and make them interchangeable.  



package strategyP;

import java.io.File;
import java.util.ArrayList;

Let's use the example of a file compression tool - where we create either zip or rar files. First we'll need a strategy: 


interface CompressionStrategy {
      public void compressFiles(ArrayList<File> files);
}


And we'll need to provide our two implementations, one for zip and one for rar

class ZipCompressionStrategy implements CompressionStrategy {

      public void compressFiles(ArrayList<File> files) {
            // using ZIP approach
      }

}

class RarCompressionStrategy implements CompressionStrategy {

      public void compressFiles(ArrayList<File> files) {
            // using RAR approach
      }
}

Our context will provide a way for the client to compress the files. Let's say that there is a preferences setting in our application that sets which compression algorithm to use. We can change our strategy using the setCompressionStrategy method in the Context.

class CompressionContext {
      private CompressionStrategy strategy;

      // this can be set at runtime by the application preferences
      public void setCompressionStrategy(CompressionStrategy strategy) {
            this.strategy = strategy;
      }

      // use the strategy
      public void createArchive(ArrayList<File> files) {
            strategy.compressFiles(files);
      }

}

It's obvious that all the client has to do now is pass through the files to the CompressionContext

public class Client {

      public static void main(String[] args) {
            CompressionContext ctx = new CompressionContext();
            // we could assume context is already set by preferences
            ctx.setCompressionStrategy(new ZipCompressionStrategy());
            // get a list of files

            ArrayList<File> fileList = null;
            ctx.createArchive(fileList);

      }
}

This example shows the WarStrategy




package strategyP;

interface WarStrategy {

      void strategyDisc();

}

class Surprise implements WarStrategy {

      @Override
      public void strategyDisc() {
            System.out.println("Surprise your enemies");
      }

}

class Maneuver implements WarStrategy {

      @Override
      public void strategyDisc() {
            System.out
                        .println("Place the enemy in a disadvantageous position through the flexible application of combat power");
}

}

class Guerrilla implements WarStrategy {

      @Override
      public void strategyDisc() {
            System.out
                        .println("Lure your opponent to attack to use guerrilla tactics effectively.");
      }
}



public class War {

      WarStrategy strategy;

      public WarStrategy getStrategy() {
            return strategy;
      }

      public void setStrategy(WarStrategy strategy) {
            this.strategy = strategy;
      }

      public static void main(String[] args) {

            War worldWarI = new War();
            worldWarI.setStrategy(new Surprise());
            worldWarI.getStrategy().strategyDisc();

            War worldWarII = new War();
            worldWarII.setStrategy(new Guerrilla());
            worldWarII.getStrategy().strategyDisc();

            War worldWarIII = new War();
            worldWarIII.setStrategy(new Maneuver());
            worldWarIII.getStrategy().strategyDisc();

      }

}











Template design pattern

In the template method of this design pattern, one or more algorithm steps can be overridden by subclasses to allow differing behaviors while ensuring that the overarching algorithm is still followed.

the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithmin a method, called template method, which defers some steps to subclasses. It lets one redefine certain steps of an algorithm without changing the algorithm's structure.


package templateP;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;

/*
 * In the template method of this design pattern,
 *  one or more algorithm steps can be overridden by subclasses
 *  to allow differing behaviors while ensuring that the
 *  overall algorithm is still followed.

 */
public abstract class QueryDatabase {

      Connection connection;

      void executeQuery(String query) {
            loadDriver();
            connection = getConnection();
            fireQuery(query);
            closeConnection();
      }

      abstract void loadDriver();

      abstract Connection getConnection();

      abstract void fireQuery(String query);

      abstract void closeConnection();
}




class QuerySQL extends QueryDatabase{

      @Override
      void loadDriver() {
            // TODO Auto-generated method stub
           
      }

      @Override
      Connection getConnection() {
            // TODO Auto-generated method stub
            return null;
      }

      @Override
      void fireQuery(String query) {
            // TODO Auto-generated method stub
           
      }

      @Override
      void closeConnection() {
            // TODO Auto-generated method stub
           
      }
     
      }


For testing purpose I have actually written the code to fire in HSQLDB as follows:


class QueryHSQLDB extends QueryDatabase {
      @Override
      void closeConnection() {

            try {
                  connection.close();
            } catch (SQLException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
            }

      }

      @Override
      void fireQuery(String s) {

            PreparedStatement pre;
            try {
                  pre = connection.prepareStatement(s);
                  ResultSet rs = pre.executeQuery();

                  if (rs != null) {
                        while (rs.next()) {
                              ResultSetMetaData rsmd = rs.getMetaData();
                              for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                                    if (i > 1) {
                                          System.out.print(",");
                                    }

                                    int type = rsmd.getColumnType(i);
                                    if (type == Types.VARCHAR || type == Types.CHAR) {
                                          System.out.print(rs.getString(i));
                                    } else {
                                          System.out.print(rs.getLong(i));
                                    }
                              }

                              System.out.println();
                        }
                  }
                 
                  rs.close();

            } catch (SQLException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
            }
           
      }

      @Override
      Connection getConnection() {
            Connection con = null;
            try {
                  con = DriverManager.getConnection("jdbc:hsqldb:hsql://localhost/",
                              "sa", "");
            } catch (SQLException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
            }
            return con;

      }

      @Override
      void loadDriver() {

            try {
                  Class.forName("org.hsqldb.jdbcDriver");
            } catch (ClassNotFoundException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
            }

      }

}

class QueryDB2 extends QueryDatabase {
      @Override
      void closeConnection() {

            try {
                  connection.close();
            } catch (SQLException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
            }

      }

      @Override
      void fireQuery(String s) {
            // TODO Auto-generated method stub
      }

      @Override
      Connection getConnection() {
            Connection con = null;
            try {
                  con = DriverManager.getConnection(
                              "jdbc:db2:@krishnaPc:1008:MAHADEV", "usrname", "xxxx");
            } catch (SQLException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
            }
            return con;

      }

      @Override
      void loadDriver() {

            try {
                  Class.forName("com.ibm.db2.jcc.DB2Driver");
            } catch (ClassNotFoundException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
            }

      }
}


Now time to test our code:
package templateP;

public class QueryDatabaseTest {
      public static void main(String[] args) {
            QueryHSQLDB queryHSLDB = new QueryHSQLDB();
            queryHSLDB.executeQuery("SELECT * FROM CUSTOMER");
           
            QueryDB2  db2 = new QueryDB2();
            db2.executeQuery("SELECT * FROM CUSTOMER");
           
      }
}


Result:
1,adi,India,Aditya Sharma
2,priya,India,Priyanka Sharma

java.lang.ClassNotFoundException: com.ibm.db2.jcc.DB2Driver