Home > Articles > Software Development & Management

  • Print
  • + Share This
This chapter is from the book

This chapter is from the book

Replace Conditional Dispatcher with Command

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.

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.
  2. → Compile and test.

  3. Repeat step 1 to extract all remaining chunks of request-handling code into execution methods.
  4. 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).
  5. → 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).

  6. 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.

  7. Make every concrete command implement or extend your command and update all client code to work with each concrete command via the command type.
  8. → Compile and test.

  9. 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.
  10. 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.

  11. 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].
  12. → 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():
  2. 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.

  3. I now go on to extract the next chunk of request-handling code, which deals with listing all workshops in the catalog:
  4. 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.

  5. Now I begin creating concrete commands. I first produce the NewWorkshopHandler concrete command by applying Extract Class [F] on the execution method getNewWorkshopResponse():
  6. 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).

  7. 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):
  8.    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.

  9. 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:
  10.    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.

  11. 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:
  12. 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.

  13. 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.

  • + Share This
  • 🔖 Save To Your Account

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020