Replace Conditional Dispatcher with Command

Prev don't be afraid of buying books Next

Replace Conditional Dispatcher with Command

Conditional logic is used to dispatch requests and execute actions.



Create a Command for each action. Store the Commands in a collection and replace the conditional logic with code to fetch and execute Commands.



Motivation

Many systems receive, route, and handle requests. A conditional dispatcher is a conditional statement (such as a switch) that performs request routing and handling. Some conditional dispatchers are well suited for their jobs; others aren't.

Conditional dispatchers that are well suited for their jobs tend to route a small number of requests to small chunks of handler logic. Such dispatchers can often be viewed on a monitor without having to scroll to see all of the code. The Command pattern usually doesn't provide a useful replacement for these kinds of conditional dispatchers.

On the other hand, if your conditional dispatcher is small, it may still not be a good fit for your system. The two most common reasons to refactor from a conditional dispatcher to a Command-based solution are the following.

  1. Not enough runtime flexibility: Clients that rely on the conditional dispatcher develop a need to dynamically configure it with new requests or handler logic. Yet the conditional dispatcher doesn't allow for such dynamic configurations because all of its routing and handling logic is hard-coded into a single conditional statement.

  2. A bloated body of code: Some conditional dispatchers become enormous and unwieldy as they evolve to handle new requests or as their handler logic becomes ever more complex with new responsibilities. Extracting the handler logic into different methods doesn't help enough because the class that contains the dispatcher and extracted handler methods is still too large to work with.

The Command pattern provides an excellent solution to such problems. To implement it, you simply place each piece of request-handling logic in a separate "command" class that has a common method, like execute() or run(), for executing its encapsulated handler logic. Once you have a family of such commands, you can use a collection to store and retrieve instances of them; add, remove, or change instances; and execute instances by invoking their execution methods.

Routing requests and executing diverse behavior in a uniform way may be so central to a design that you may find yourself using the Command pattern early, rather than refactoring to it later. Many of the server-side, Web-based systems I've built have used the Command pattern to produce a standard way to route requests, execute actions, or forward actions to other actions. The Example section shows how to refactor to such a solution.

The authors of Design Patterns [DP] explain how the Command pattern is often used to support an undo/redo capability. A question that often arises in extreme programming (XP) circles is what to do when you aren't sure whether a system will need undo/redo. Do you just implement the Command pattern in case the need arises? Or is that a violation of "You aren't gonna need it," an XP principle that cautions against adding functionality to code based on speculation, not genuine need. If I'm not sure whether a system needs the Command pattern, I generally don't implement it, for I find that it isn't that hard to refactor to this pattern when the need arises. However, if your code is getting into a state in which it will be harder and harder to refactor to the Command pattern and there's a good chance you'll soon need an undo/redo capability, it may make sense to refactor it to use Command before doing so will be impossibly hard. It's a bit like taking out an insurance plan.

The Command pattern is easy to implement, versatile, and incredibly useful. This refactoring captures only one area in which it is useful. Because Command can solve other tricky problems, there could easily be additional refactorings to it.

Benefits and Liabilities

+

Provides a simple mechanism for executing diverse behavior in a uniform way.

+

Enables runtime changes regarding which requests are handled and how.

+

Requires trivial code to implement.

Complicates a design when a conditional dispatcher is sufficient.







Mechanics

1. On a class containing a conditional dispatcher, find code that handles a request and apply Extract Method [F] on that code until you have an execution method, a method that invokes the code's behavior.

  • Compile and test.

2. Repeat step 1 to extract all remaining chunks of request-handling code into execution methods.

3. Apply Extract Class [F] on each execution method to produce a concrete command, a class that handles a request. This step usually implies making the execution method on the concrete command public. If the execution method in the new concrete command is too large or not quickly understandable, apply Compose Method (123).

  • Compile and test.

After you've finished creating all of your concrete commands, look for duplicated code in them. If you find some, see if you can remove it by applying Form Template Method (205).

4. Define a command, an interface or abstract class that declares an execution method that is the same for every concrete command. To implement this step, you'll need to analyze your concrete commands to learn what's unique or similar about them. Find answers to the following questions.

  • What parameter(s) must be passed to a common execution method?

  • What parameter(s) could be passed during a concrete command's construction?

  • What information could a concrete command obtain by calling back on a parameter, rather than having data passed direcly to the concrete command?

  • What is the simplest signature for an execution method that is the same for every concrete command?

Consider producing an early version of your command by applying Extract Superclass [F] or Extract Interface [F] on a concrete command.

  • Compile.

5. Make every concrete command implement or extend your command and update all client code to work with each concrete command via the command type.

  • Compile and test.

6. On the class that contains the conditional dispatcher, define and populate a command map, a map that contains instances of each concrete command, keyed by a unique identifier (e.g., a command name) that may be used at runtime to fetch a command.

If you have many concrete commands, you'll have a lot of code that adds concrete command instances to your command map. In that case, consider making your concrete commands implement the Plugin pattern, from Patterns of Enterprise Application Architecture [Fowler, PEAA]. This will make it possible for them to be loaded simply by supplying the appropriate configuration data (such as a list of the names of the command classes or, even better, a directory where the classes live).

  • Compile.

7. On the class that contains the conditional dispatcher, replace the conditional code for dispatching requests with code to fetch the correct concrete command and execute it by calling its execution method. This class is now an Invoker [DP, 236].

  • Compile and test.

Example

The example code we'll look at comes from a system I cowrote to create and organize Industrial Logic's HTML-based catalogs. Ironically, this system made heavy use of the Command pattern from its earliest evolutions. I decided to rewrite the sections of the system that used the Command pattern to not use the Command pattern in order to produce the kind of bloated, Command-thirsty code that I so frequently encounter in the field.

In the altered code, a class named CatalogApp is responsible for dispatching and executing actions and returning responses. It performs this work within one large conditional statement:

 public class CatalogApp...   private HandlerResponse executeActionAndGetResponse(String actionName, Map parameters)...     if (actionName.equals(NEW_WORKSHOP)) {       String nextWorkshopID = workshopManager.getNextWorkshopID();       StringBuffer newWorkshopContents =         workshopManager.createNewFileFromTemplate(           nextWorkshopID,           workshopManager.getWorkshopDir(),           workshopManager.getWorkshopTemplate()         );       workshopManager.addWorkshop(newWorkshopContents);       parameters.put("id",nextWorkshopID);       executeActionAndGetResponse(ALL_WORKSHOPS, parameters);     } else if (actionName.equals(ALL_WORKSHOPS)) {       XMLBuilder allWorkshopsXml = new XMLBuilder("workshops");       WorkshopRepository repository =         workshopManager.getWorkshopRepository();       Iterator ids = repository.keyIterator();       while (ids.hasNext()) {         String id = (String)ids.next();         Workshop workshop = repository.getWorkshop(id);         allWorkshopsXml.addBelowParent("workshop");         allWorkshopsXml.addAttribute("id", workshop.getID());         allWorkshopsXml.addAttribute("name", workshop.getName());         allWorkshopsXml.addAttribute("status", workshop.getStatus());         allWorkshopsXml.addAttribute("duration",           workshop.getDurationAsString());       }       String formattedXml = getFormattedData(allWorkshopsXml.toString());       return new HandlerResponse(         new StringBuffer(formattedXml),         ALL_WORKSHOPS_STYLESHEET       );     } ...many more "else if" statements 

The complete conditional spans several pages—I'll spare you the details. The first leg of the conditional handles the creation of a new workshop. The second leg, which happens to be called by the first leg, returns XML that contains summary information for all of Industrial Logic's workshops. I'll show how to refactor this code to use the Command pattern.

  1. I start by working on the first leg of the conditional. I apply Extract Method [F] to produce the execution method getNewWorkshopResponse():

     public class CatalogApp...   private HandlerResponse executeActionAndGetResponse(String actionName, Map parameters)...     if (actionName.equals(NEW_WORKSHOP)) {        getNewWorkshopResponse(parameters);     } else if (actionName.equals(ALL_WORKSHOPS)) {       ...     } ...many more "else if" statements    private void getNewWorkshopResponse(Map parameters) throws Exception {      String nextWorkshopID = workshopManager.getNextWorkshopID();      StringBuffer newWorkshopContents =        workshopManager.createNewFileFromTemplate(          nextWorkshopID,          workshopManager.getWorkshopDir(),          workshopManager.getWorkshopTemplate()        );      workshopManager.addWorkshop(newWorkshopContents);      parameters.put("id",nextWorkshopID);      executeActionAndGetResponse(ALL_WORKSHOPS, parameters);    } 

    The compiler and test code are happy with the newly extracted method.

  2. I now go on to extract the next chunk of request-handling code, which deals with listing all workshops in the catalog:

     public class CatalogApp...   private HandlerResponse executeActionAndGetResponse(String actionName, Map parameters)...     if (actionName.equals(NEW_WORKSHOP)) {       getNewWorkshopResponse(parameters);     } else if (actionName.equals(ALL_WORKSHOPS)) {        getAllWorkshopsResponse();     } ...many more "else if" statements    public HandlerResponse getAllWorkshopsResponse() {      XMLBuilder allWorkshopsXml = new XMLBuilder("workshops");      WorkshopRepository repository =        workshopManager.getWorkshopRepository();      Iterator ids = repository.keyIterator();      while (ids.hasNext()) {        String id = (String)ids.next();        Workshop workshop = repository.getWorkshop(id);        allWorkshopsXml.addBelowParent("workshop");        allWorkshopsXml.addAttribute("id", workshop.getID());        allWorkshopsXml.addAttribute("name", workshop.getName());        allWorkshopsXml.addAttribute("status", workshop.getStatus());        allWorkshopsXml.addAttribute("duraction",          workshop.getDurationAsString());      }      String formattedXml = getFormattedData(allWorkshopsXml.toString());      return new HandlerResponse(        new StringBuffer(formattedXml),        ALL_WORKSHOPS_STYLESHEET      );    } 

    I compile, test, and repeat this step for all remaining chunks of request-handling code.

  3. Now I begin creating concrete commands. I first produce the NewWorkshopHandler concrete command by applying Extract Class [F] on the execution method getNewWorkshopResponse():

      public class NewWorkshopHandler {    private CatalogApp catalogApp;    public NewWorkshopHandler(CatalogApp catalogApp) {      this.catalogApp = catalogApp;    }    public HandlerResponse getNewWorkshopResponse(Map parameters) throws Exception {      String nextWorkshopID = workshopManager().getNextWorkshopID();      StringBuffer newWorkshopContents =        WorkshopManager().createNewFileFromTemplate(          nextWorkshopID,          workshopManager().getWorkshopDir(),          workshopManager().getWorkshopTemplate()        );      workshopManager().addWorkshop(newWorkshopContents);      parameters.put("id", nextWorkshopID);      catalogApp.executeActionAndGetResponse(ALL_WORKSHOPS, parameters);    }    private WorkshopManager workshopManager() {      return catalogApp.getWorkshopManager();    }  } 

    CatalogApp instantiates and calls an instance of NewWorkshopHandler like so:

     public class CatalogApp...    public HandlerResponse executeActionAndGetResponse(     String actionName, Map parameters) throws Exception {     if (actionName.equals(NEW_WORKSHOP)) {        return new NewWorkshopHandler(this).getNewWorkshopResponse(parameters);     } else if (actionName.equals(ALL_WORKSHOPS)) {       ...     } ... 

    The compiler and tests confirm that these changes work fine. Note that I made executeActionAndGetResponse(…) public because it's called from NewWorkshopHandler.

    Before I go on, I apply Compose Method (123) on NewWorkshopHandler's execution method:

      public class NewWorkshopHandler...    public HandlerResponse getNewWorkshopResponse(Map parameters) throws Exception {      createNewWorkshop(parameters);      return catalogApp.executeActionAndGetResponse(        CatalogApp.ALL_WORKSHOPS, parameters);    }    private void createNewWorkshop(Map parameters) throws Exception {      String nextWorkshopID = workshopManager().getNextWorkshopID();      workshopManager().addWorkshop(newWorkshopContents(nextWorkshopID));      parameters.put("id",nextWorkshopID);    }    private StringBuffer newWorkshopContents(String nextWorkshopID) throws Exception {      StringBuffer newWorkshopContents = workshopManager().createNewFileFromTemplate(        nextWorkshopID,        workshopManager().getWorkshopDir(),        workshopManager().getWorkshopTemplate()      );      return newWorkshopContents;    } 

    I repeat this step for additional execution methods that ought to be extracted into their own concrete commands and turned into Composed Methods. AllWorkshopsHandler is the next concrete command I extract. Here's how it looks:

      public class AllWorkshopsHandler...    private CatalogApp catalogApp;    private static String ALL_WORKSHOPS_STYLESHEET="allWorkshops.xsl";    private PrettyPrinter prettyPrinter = new PrettyPrinter();    public AllWorkshopsHandler(CatalogApp catalogApp) {      this.catalogApp = catalogApp;    }    public HandlerResponse getAllWorkshopsResponse() throws Exception {      return new HandlerResponse(        new StringBuffer(prettyPrint(allWorkshopsData())),        ALL_WORKSHOPS_STYLESHEET      );    }    private String allWorkshopsData() ...    private String prettyPrint(String buffer) {      return prettyPrinter.format(buffer);    } 

    After performing this step for every concrete command, I look for duplicated code across all of the concrete commands. I don't find much duplication, so there is no need to apply Form Template Method (205).

  4. I must now create a command (as defined in the Mechanics section, an interface or abstract class that declares an execution method that every concrete command must implement). At the moment, every concrete command has an execution method with a different name, and the execution methods take a different number of arguments (namely, one or none):

        if (actionName.equals(NEW_WORKSHOP)) {      return new NewWorkshopHandler(this). getNewWorkshopResponse(parameters);    } else if (actionName.equals(ALL_WORKSHOPS)) {      return new AllWorkshopsHandler(this). getAllWorkshopsResponse();    } ... 

    Making a command will involve deciding on:

    • A common execution method name

    • What information to pass to and obtain from each handler

    The common execution method name I choose is execute (a name that's often used when implementing the Command pattern, but by no means the only name to use). Now I must decide what information needs to be passed to and/or obtained from a call to execute(). I survey the concrete commands I've created and learn that a good many of them:

    • Require information contained in a Map called parameters

    • Return an object of type HandlerResponse

    • Throw an Exception

    This means that my command must include an execution method with the following signature:

     public HandlerResponse execute(Map parameters) throws Exception 

    I create the command by performing two refactorings on NewWorkshopHandler. First, I rename its getNewWorkshopResponse(…) method to execute(…):

     public class NewWorkshopHandler...    public HandlerResponse  execute(Map parameters) throws Exception 

    Next, I apply the refactoring Extract Superclass [F] to produce an abstract class called Handler:

      public abstract class Handler {    protected CatalogApp catalogApp;    public Handler(CatalogApp catalogApp) {      this.catalogApp = catalogApp;    }  } public class NewWorkshopHandler  extends Handler...   public NewWorkshopHandler(CatalogApp catalogApp) {      super(catalogApp);   } 

    The compiler is happy with the new class, so I move on.

  5. Now that I have the command (expressed as the abstract Handler class), I'll make every handler implement it. I do this by making them all extend Handler and implement the execute() method. When I'm done, the handlers may now be invoked identically:

        if (actionName.equals(NEW_WORKSHOP)) {      return new NewWorkshopHandler(this). execute(parameters);    } else if (actionName.equals(ALL_WORKSHOPS)) {      return new AllWorkshopsHandler(this). execute(parameters);    } ... 

    I compile and run the tests to find that everything is working.

  6. Now comes the fun part. CatalogApp's conditional statement is merely acting like a crude Map. It would be better to turn it into a real map by storing an instance of my command in a command map. To do that, I define and populate handlers, a Map keyed by handler name:

     public class CatalogApp...    private Map handlers;   public CatalogApp(...) {     ...      createHandlers();     ...   }    public void createHandlers() {      handlers = new HashMap();      handlers.put(NEW_WORKSHOP, new NewWorkshopHandler(this));      handlers.put(ALL_WORKSHOPS, new AllWorkshopsHandler(this));      ...    } 

    Because I don't have too many handlers, I don't resort to implementing a Plugin, as described in the Mechanics section. The compiler is happy with the new code.

  7. Finally, I replace CatalogApp's large conditional statement with code that looks up a handler by name and executes it:

     public class CatalogApp...   public HandlerResponse executeActionAndGetResponse(     String handlerName, Map parameters) throws Exception {      Handler handler = lookupHandlerBy(handlerName);      return handler.execute(parameters);   }    private Handler lookupHandlerBy(String handlerName) {      return (Handler)handlers.get(handlerName);    } 

    The compiler and test code are happy with this Command-based solution. CatalogApp now uses the Command pattern to execute an action and get back a response. This design makes it easy to declare a new handler, name it, and register it in the command map so that it may be invoked at runtime to perform an action.

Amazon


Refactoring to Patterns (The Addison-Wesley Signature Series)
Refactoring to Patterns
ISBN: 0321213351
EAN: 2147483647
Year: 2003
Pages: 103

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net