The Command Design Pattern: Decoupling Objects and Behaviors as Objects

When one object calls a method on another object, the first object needs to have a reference to the second object. This means that the first object (the "sender" in OO parlance) needs to know the type of the second object (the "receiver"). Combined with polymorphism, where the first object holds a reference to a superclass of the second object, a lot of flexibility and power can be achieved. But there is a restriction here: the receiver must be a subclass of the type to whom the sender thinks it is talking.

That's not a terrible restriction, but what if the sender doesn't really care about what the receiver is or does? Examples of this situation are:

What we see in these scenarios is that there is a separation in the knowledge needed to fully complete the task at hand:

Let's decompose the situation in terms of variant and invariant behaviors:

Invariant
Variant
The sender calling the receiver because this process remains the same, independent of what the receiver does. The receiver and what it does because this can change without the affecting the sender.

Situations where the sender knows the exact type of the receiver are called "tightly coupled" because there is no room to vary the receiver. Systems where the sender only knows the type of the receiver's superclass are characterized as "loosely coupled" because the separation into distinct abstraction layers allows the receiver to vary. Flexible and extensible OO systems are always very loosely coupled. Tight coupling can spell disaster in complex systems because it makes the system fragile with respect to any changes.

How can we decouple the sender and receiver even more?

Interfaces to the rescue!

Let the sender talk to an interface, releasing the receiver from being constrained to a particular inheritance hierarchy. Furthermore, the interface needs only to include the method(s) that the sender needs, not any other methods that the receiver possesses but with which the sender is unconcerned. The sender has neither any idea what sort of object the receiver is any more nor what the receiver actually does, just that it has the right methods that can be called at the appropriate times.

The sender is now simply giving a "command" to the receiver in that it is telling some unknown receiver that something needs to be done. The interface represents the command that the sender wishes to invoke. The sender and receiver can be decoupled even more by using a small class that implements the command interface and simply translates the sender's call to the interface to the appropriate method call on the receiver.

This technique for decoupling the sender from its receiver is called the Command design pattern.

One of the key concepts exemplified by the Command pattern is that behaviors are being modeled as objects (the commands). Whatever desired behavior can be encapsulated into an object and passed around to finally be used by the appropriate sender. This is an extremely important and useful notion in OO systems because it means that behaviors are no longer confined to just being methods inseparable from their classes.

 

Examples

In the UML class diagram below, Client is the sender, so it holds a reference to an ICommand interface. The ICommand interface has one method, execute()which takes an integer. When the Client's run() method is called, the ICommand's execute() may be called. In the supplied code, the command is invoked (it's execute()method called) if the integer supplied to run() is larger than an internally generated random number between 0 and 10. The random number is given to the command when it is invoked.

Discussion of the various ICommands:

Here is the code for everything except Command3, Command4 and Command5 (download Command0.java):

interface ICommand {
  void execute(int x);
}

class Client {
  ICommand c;
  
  void setCommand(ICommand c) {
    this.c = c;  
  }
  
  void run(int i){
    // Math.random() returns a double between >=0.0 and < 1.0
    int j = (int) (10.0*Math.random()); 
    if(i > j) {
      System.out.println(i+" > "+j);
      c.execute(j);
    }
    else {
      System.out.println(i+" < "+j);
    }
  }
} 

class Command1 implements ICommand {
  public void execute(int x) {
    System.out.println("Yahoo! " + x);
  }
}

class Command2 implements ICommand {
  public void execute(int x) {
    System.out.println("Mamma mia!" + x);
  }
}

class PhraseMaker {
  void phrase1() {
    System.out.println("Java:  More fun under the Sun.");
  }
  
  void phrase2() {
    System.out.println("C#: Bill G's vision when he gets old?");
  }
}

class CommandFrame extends javax.swing.JFrame implements ICommand {
  public void execute(int x) {
    int y = 100*x;  // will need this value many times
    setTitle("The frame is "+y+" x "+y+" pixels");  // sets the title of the frame
    setSize(y,y); // sets the size of the square frame 
    show();  // show the frame if not visible
  }  
}

class PhraseMaker {
  void phrase1() {

    System.out.println("Java:  More fun under the Sun.");
  	}
  
  	void phrase2() {
    	System.out.println("C#: Bill G's vision when he gets old?");
  	}
}

Note: The identifier javax.swing.JFrame above tells the compiler where to find the JFrame class. It says that JFrame is in the swing package, which is in the javax package. Packages control accessibility at a multi-class level. For more on Java accessibility and scoping features see the page on scoping in the Java Resources site.

Exercises

First, download the above code (Command0.java) and check that it compiles it correctly.

  1. Test Client, Command1, Command2 and CommandFrame classes by
    1. Instantiating one object for each of the above classes.
    2. Set the ICommand in the Client object to one of the command objects.
    3. Repeatedly call the Client's run() method with a number between 0 and 10.
  2. Write the Command3 class. Test it with Command1 and Command2.
  3. Write the Command4 and Command5 classes. Test them carefully with the all the other commands.
  4. Mix and match commands to explore what happens with various combinations.

Solution: NO PEEKING!! (Command1.java)

(Written by Stephen Wong)