Back to Thoughts On Technology Page | Back to Peter's Home Page | Back to Family Home Page



The Command Design Pattern
Peter RosePeter Rose - 01/2005


ABSTRACT

SUMMARY

HOW THE PATTERN WORKS

COMMON PARAMETER REQUEST HANDLING

REPLACING CONDITIONAL LOGIC WITH OBJECTS

IMPLEMENTING THE COMMAND DESIGN PATTERN

APPENDIX: SAMPLE PROGRAM OUTPUT

APPENDIX: FULL CODE




Abstract
The Command Design Pattern evaluates a request, instantiates and returns an appropriate concrete object of some general abstract class type, and then polymorphicly calls inherited methods on that concrete object to carry out the business of the class. Thus, each business request from a client object can be handled by a separate highly encapsulated and loosely coupled object.

Summary
This example will show how the Command Design Pattern can be used to greatly simplify the conditional logic using a servlet's HttpServletRequest action parameter command.

A servlet's doPost() method (or some other referenced main controlling method in the servlet) is easily overrun with huge conditional logic for each flavor of action command parameter that it is sent. Many times, not only are there huge numbers of these conditional statements to evaluate, but each conditional block keeps getting business logic added to it to the point of creating much unneeded duplication of code and subsequent tight couple of objects that respond to requirements from these blocks.

The Command Design Pattern not only abstracts this conditional logic out into actual concrete classes, but it can also be simplified to the extent that the doPost() method in the servlet only consists of a few lines of code.

How The Pattern Works
A simple example of a common problem the Command Design Pattern is well suited to solve is the evaluation and execution of an incoming HttpServletRequest action parameter command from a submit on a JSP page.

Most servlets in the M-V-C Model 2 form (i.e. Struts framework type) redirect both their doGet() and doPost() methods to a perform() or performTask() method after stripping out the relevant data from the HttpServletRequest. Thus, we can pass an already constructed object graph the system will then manipulate. To keep things simple, we will just pass the performTask() method of our servlet a String action parameter such as "create", "replace", "update", etc.

In pre-design pattern days, these action parameters would be handled via large blocks of conditional logic in the servlet. Then it was seen that refactoring this logic out into separate private or protected methods produced simpler to read code. However, as web based applications demonstrated the complex logic needed in servlet code, it was seen that groups of these methods solving related business issues really needed to become separate servlets or just regular classes in their own right. This was the separation into the M-V-C design pattern of solution where a controller servlet delegated its work to other specialized servlets or classes.

But the conditional logic in these receiving servlets was still too complex to deal with in the doPost() or performTask() method. The Command Design Pattern provided a way to meld object inheritance among an abstract factory class with its discrete and highly encapsulated and loosely coupled descendant concrete classes to simplify the controlling servlet's api and work load.

This document will look at these 3 ways of accomplishing the same goal:

Common Parameter Request Handling
Since we will be refactoring this method into a Command Design Pattern, this first example will call the performTask1() method as follows:


	// ===================================================================
	private void performTask1(String action) {
		//-- Stuffs all business logic into the conditional statements
		if( action.equalsIgnoreCase("create") ) {
			CommandExample.log(3,"create conditional business logic.");
		} else if( action.equalsIgnoreCase("replace") ) {
			CommandExample.log(3,"replace conditional business logic.");
		} else if( action.equalsIgnoreCase("update") ) {
			CommandExample.log(3,"update conditional business logic.");
		} else if( action.equalsIgnoreCase("delete") ) {
			CommandExample.log(3,"delete conditional business logic.");
		} //endif
	} //endmethod: performTask1

The performTask1() method evaluates the action parameter in its conditional statements, performs some housekeeping and validation perhaps, and then several hundred lines of code later redirects control to some jsp page or back to a master controller servlet. Envision each of the above conditionals to contain from 50 to several hundred lines of code to resolve the action parameter's business request.

The point is that these blocks of conditional logic can get really messy. It is not uncommon to find 8 sublevels deep of other conditional processing embedded within the main conditional blocks. Maintenance on such code readily becomes problematic. An obvious immediate solution is to simply package all of an action parameter conditional block's logic into a method.

This is unsatisfactory in the case of action parameters from a servlet. These action parameters do not usually imply doing a simple operation or calculation and then returning. More often than not, there is complex business logic involved with the need to instantiate many other objects and even complex graphs of objects as well as then routing processing control to some other object (again in the case of servlets to a jsp).

Objects are best suited to deal with this type of functionality. Our example performTask1() method appears to be dealing with complex database manipulation. Of course in a real example, in addition to the action parameter being passed into it, you would also get the object or objects needed for the operation. Things thus get very messy very fast.

Replacing Conditional Logic With Objects
This leads us to our first refactoring of this example, the performTask2() method.


	// ===================================================================
	private void performTask2(String action) {
		//-- Abstracts out all business logic into separate classes
		ActionType cmd = null;

		if( action.equalsIgnoreCase("create") ) {
			cmd = new ActionTypeCreate();
		} else if( action.equalsIgnoreCase("replace") ) {
			cmd = new ActionTypeReplace();
		} else if( action.equalsIgnoreCase("update") ) {
			cmd = new ActionTypeUpdate();
		} else if( action.equalsIgnoreCase("delete") ) {
			cmd = new ActionTypeDelete();
		} //endif
		cmd.execute();
	} //endmethod: performTask2


This considerably cleans up the code in the performTask() method of the servlet. However, such code structure begs to be meddled with by a developer who adds business logic here to avoid thinking about the big picture of the encapsulation of business requirements where they best belong. The Command Design Pattern enforces this encapsulation and loose coupling of objects.

Implementing The Command Design Pattern
Here is the performTask() method of a servlet that uses the Command Design Pattern:


	// ===================================================================
	private void performTask3(String action) {
		ActionType cmd = (ActionType)commandMap.get(action);
		cmd.execute();
	} //endmethod: performTask3


In order to facillitate this simple pattern, the following code is inserted at the top of the servlet and executed in either the constructor or an init method as shown:


// ************************************************************************
class ExampleServlet  {
	private HashMap commandMap = new HashMap();

	// ===================================================================
	public ExampleServlet() {
		commandMap.put("create", new ActionTypeCreate());
		commandMap.put("replace", new ActionTypeReplace());
		commandMap.put("update", new ActionTypeUpdate());
		commandMap.put("delete", new ActionTypeDelete());
	} //endmain

	// ===================================================================
	private void performTask3(String action) {
		ActionType cmd = (ActionType)commandMap.get(action);
		cmd.execute();
	} //endmethod: performTask3

} //endclass: ExampleServlet


This servlet may have been instantiated from the ControllerServlet after the controller determined that the request was for a database operation - which could have in turn been determined via a Command Design Pattern there as well.

Each class thus only does exactly one thing keeping everything very simple, i.e. loosely coupled and highly cohesive (encapsulated).

APPENDIX: Sample Program Output
The following is a sample run of the CommandExample.java file


   ---> Begin Command Design Pattern example.
   ---> Simulated servlet request received is: update
   ---> Code will show 3 approaches as to how such a command can be processed.
     ---> Demonstrating from full conditional processing.
       ---> update business logic executing...
     ---> Demonstrating from abstracted concrete class processing.
       ---> ActionTypeUpdate business logic executing...
     ---> Demonstrating from Command Design Pattern processing.
       ---> ActionTypeUpdate business logic executing...
   ---> End Command Design Pattern example.


APPENDIX: Full Code


// CommandExample.java

import java.util.HashMap;

// ************************************************************************
/**
 * CommandExample.java
 *
 * Provides a simple Command Design Pattern Example.
 *
 * The example will show how to use the Command Design Pattern
 * by replacing conditional control logic in a simulated M-V-C Model
 * 2 servlet. So as not to need a webserver to implement this
 * example, a phamtom object will be created and passed in for
 * demonstration purposes.
*/
// ************************************************************************
public class CommandExample {
	public static void main(String[] args) {
		CommandExample.log(1,"Begin Command Design Pattern example.");
		ExampleServlet exampleServlet = new ExampleServlet();
		int maxApproaches = 3;
		String exampleActionRequest = "update";

		CommandExample.log(1,"Simulated servlet request received is: " +
			exampleActionRequest);
		CommandExample.log(1,"Code will show " + maxApproaches +
			" approaches as to how such a command can be processed.");
		for(int i=1; i<=maxApproaches; i++) {
			exampleServlet.doPost(i,exampleActionRequest);
		} //endfor
		CommandExample.log(1,"End Command Design Pattern example.");
	} //endmain

	// ===================================================================
	public static void log(int aLevel, String aMsg) {
		boolean showLogTraceToSystemOut = true;
	  	if(showLogTraceToSystemOut) {
			String prefix = "";
		  	if(aLevel == 1) {
				prefix = "   ---> ";
		  	} else if(aLevel == 2) {
				prefix = "     ---> ";
		  	} else if(aLevel == 3) {
				prefix = "       ---> ";
		  	} //endif
		  	System.out.println(prefix + aMsg);
	  	} //endif
  	} //endmethod: log
} //endclass: CommandExample.java



// ************************************************************************
/**
 * ExampleServlet.java
 *
 * Simulates a servlet in an M-V-C Model 2 environment.
 *
 * @param action the desired action the servlet is to perform.
*/
// ************************************************************************
class ExampleServlet  {
	private HashMap commandMap = new HashMap();

	// ===================================================================
	public ExampleServlet() {
		//-- Initialize HashMap of possible ActionType concrete objects
		commandMap.put("create", new ActionTypeCreate());
		commandMap.put("replace", new ActionTypeReplace());
		commandMap.put("update", new ActionTypeUpdate());
		commandMap.put("delete", new ActionTypeDelete());
	} //endmain

	// ===================================================================
	public void doPost(int approachSolution, String action) {
		switch (approachSolution) {
		case 1:
			CommandExample.log(2,
			"Demonstrating from full conditional processing.");
			performTask1(action);
			break;
		case 2:
			CommandExample.log(2,
			"Demonstrating from abstracted concrete class processing.");
			performTask2(action);
			break;
		case 3:
			CommandExample.log(2,
			"Demonstrating from Command Design Pattern processing.");
			performTask3(action);
			break;
		} //endswitch


	} //endmethod: doPost

	// ===================================================================
	private void performTask1(String action) {
		//-- Process business logic within conditional statements
		if( action.equalsIgnoreCase("create") ) {
			CommandExample.log(3,
				"create business logic executing...");
		} else if( action.equalsIgnoreCase("replace") ) {
			CommandExample.log(3,
				"replace business logic executing...");
		} else if( action.equalsIgnoreCase("update") ) {
			CommandExample.log(3,
				"update business logic executing...");
		} else if( action.equalsIgnoreCase("delete") ) {
			CommandExample.log(3,
				"delete business logic executing...");
		} //endif
	} //endmethod: performTask1

	// ===================================================================
	private void performTask2(String action) {
		//-- Abstracts out all business logic into separate classes
		ActionType cmd = null;

		if( action.equalsIgnoreCase("create") ) {
			cmd = new ActionTypeCreate();
		} else if( action.equalsIgnoreCase("replace") ) {
			cmd = new ActionTypeReplace();
		} else if( action.equalsIgnoreCase("update") ) {
			cmd = new ActionTypeUpdate();
		} else if( action.equalsIgnoreCase("delete") ) {
			cmd = new ActionTypeDelete();
		} //endif
		cmd.execute();
	} //endmethod: performTask2

	// ===================================================================
	private void performTask3(String action) {
		//-- Uses the Command Design Pattern to polymorphically
		//-- execute desired action
		ActionType cmd = (ActionType)commandMap.get(action);
		cmd.execute();
	} //endmethod: performTask3
} //endclass: ExampleServlet


// ************************************************************************
abstract class ActionType {
	abstract public void execute();
} //endclass: ActionType

// ************************************************************************
class ActionTypeCreate extends ActionType {
	public void execute() {
		CommandExample.log(3,
			"ActionTypeCreate business logic executing...");
	} //endmethod: execute
} //endclass: ActionTypeCreate

// ************************************************************************
class ActionTypeReplace extends ActionType {
	public void execute() {
		CommandExample.log(3,
			"ActionTypeReplace business logic executing...");
	} //endmethod: execute
} //endclass: ActionTypeReplace

// ************************************************************************
class ActionTypeUpdate extends ActionType {
	public void execute() {
		CommandExample.log(3,
			"ActionTypeUpdate business logic executing...");
	} //endmethod: execute
} //endclass: ActionTypeUpdate

// ************************************************************************
class ActionTypeDelete extends ActionType {
	public void execute() {
		CommandExample.log(3,
			"ActionTypeDelete business logic executing...");
	} //endmethod: execute
} //endclass: ActionTypeDelete



Back to Thoughts On Technology Page | Back to Peter's Home Page | Back to Family Home Page