When hiring an electrician to fix a faulty light switch, you don't need to know how wiring works, just that something needs to be done to fix it. While this may seem painfully intuitive, it actually models a powerful design pattern called Command.

Assumptions

What is the Command Pattern?

The Command Pattern is a behavioral design pattern that encapsulates all the information needed to invoke a method of a different class for a later time. Usually, this comes in the form of an execute() method.

<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/CommandPattern_01.png" alt="command pattern UML diagram." style="max-width:95%;"> </div>

Key components:

  • Command: an interface that defines a parameter-free execute method.
  • Concrete Command: a class that implements the command interface, and stores the state needed to invoke.
  • Invoker: stores a command and carries out the request.
  • Receiver: the class that is acted upon by the concrete command.

Because the state needed for the command is self contained, it is popularly used for implementing undo() and redo() functionality in programs!

Conceptualization

For this conceptualization, we'll be modeling a basic web browser that let's us go to a site and add (and remove) bookmarks. We have a working class already:

// Our Receiver class that we'll add commands for:
class WebBrowser { 
   List&lt;String&gt; bookmarks;
   String currentUrl;
   
   WebBrowser() {
     bookmarks = new List&lt;String&gt;();
     currentUrl = null;
   }
   
      String GetCurrentUrl() {
     return currentUrl;
   }
   
   void navigate(String url) {
     this.currentUrl = urll
     print("Navigated to " + url);
   }
   
   // Bookmark the current page that the user is on:
   void bookmarkCurrentPage() {
     if(currentUrl &amp;&amp; this.bookmarks.contains(currentUrl) == false)
     {
       this.bookmarks.add(currentUrl);
     }
   }
   
   void removeBookmark(String url) {
     this.bookmarks.remove(url);
   }
 }

To help translate keyboard input, we want to create a list of command classes that will maintain the needed state to execute these methods. First, let's define the interface:

// Our Command interface:
interface ICommand {
   void execute();
 }

// Bonus! An undo/redo interface:
interface IUndoableCommand extends ICommand {
   void undo();
   void redo();
 }

Every class that implements ICommand will be able to call execute() to carry out the command at a later time. In order to call out the method without any parameters, though, we need to insert the state on object construction.

To capture the bookmarking functionality, we'll make a command class: AddBookmarkCommand

> We're leaving out RemoveBookmarkCommand because we'll be using undo and redo!

// Adds a bookmark: (undoable!)
class AddBookmarkCommand implements IUndoableCommand {
   WebBrowser browser;
   String url;
   
   AddBookmarkCommand(WebBrowser browser) {
     this.browser = browser;
     this.url = null;
   }
   
   void execute() {
     // We store this data away so that we can undo if we need to.
     this.url = browser.GetCurrentUrl();
     browser.bookmarkCurrentPage();
   }
   
   void undo() {
     browser.removeBookmark(url);
   }
   
   // Redo commands *usually* only run the execute method:
   void redo() {
     execute();
   }
 }
// It would be easy to add a remove class if it was needed!

Now that we have our command class, we could use it in the client code as-is; however, there is one element to the command pattern that hasn't been accounted for yet: the invoker. Usually this class may accept a command and invoke it when requested, however we'll be using it to hold our undo and redo commands. To capture this in the context of our web browser, we'll create a bookmarker to store executed commands.

// Invoker class for our undoable command type, we'll call it bookmarker:
class Bookmarker {
   Stack&lt;IUndoableCommand&gt; undoStack;
   Stack&lt;IUndoableCommand&gt; redoStack;
   WebBrowser browser;
   
   Bookmarker(WebBrowser browser) {
     this.undoStack = new Stack&lt;IUndoableCommand&gt;();
     this.redoStack = new Stack&lt;IUndoableCommand&gt;();
     this.browser = browser;
   }
   
   void bookmarkCurrentPage() {
     // Create a new bookmark entity:
     IUndoableCommand command = new AddBookmarkCommand(this.browser);
     // Add it to the undo stack:
     this.undoStack.Push(command);
     // Clear the redo stack:
     this.redoStack.Clear();
     
     // Execute the bookmark command:
     command.execute();
   }
   
   void undoBookmark() {
     // Get an undo off of the stack:
     if(!this.undoStack.IsEmpty())
     {
       IUndoableCommand command = this.undoStack.Pop();
       // Push the command to the redo stack:
       this.redoStack.Push(command);
       // Execute the undo on the command:
       command.undo();
     }
   }
   
   void redoBookmark() {
     if(!this.redoStack.IsEmpty())
     {
       IUndoableCommand command = this.redoStack.Pop();
       // Push the command to the undo stack:
       this.undoStack.Push(command);
       // Execute the redo command:
       command.redo();
     }
   }
 }

With our invokers in place, we can see how they all fit together in the client code:

static void main(String[] args) {
   // Create the web browser:
      WebBrowser memeSurfer = new WebBrowser();
   // Create the invoker:
   Bookmarker bookmarker = new Bookmarker(memeSurfer);
   
   // Navigate to a website:
   memeSurfer.navigate("dankMemes.gov");
     // Bookmark it:
   bookmarker.bookmarkCurrentPage();
   
   // Navigate to another site:
   memeSurver.navigate("normieMemes.co");
   // Bookmark that site too:
   bookmarker.bookmarkCurrentPage();
   // On second thought, maybe not:
   bookmarker.undoBookmark();
   // Oh, what the heck, I'll allow it:
   bookmarker.redoBookmark();
   
 }

> Challenge: implement navigation history!