April 19, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Working with Design Patterns: Command, Page 2

  • April 13, 2007
  • By Jeff Langr
  • Send Email »
  • More Articles »

By using this refactoring pattern, I can incrementally put such command classes in place. As I tackle the second command class, I recognize that both commands support the common method execute. I introduce an interface, Command (see Listing 7), and alter each command class to implement this interface.

Listing 7. The Command interface.

public interface Command {
   void execute();
}

I am now able to introduce a factory class whose sole job is to return Command objects (see Listing 8).

Listing 8. The Command interface.

public class CommandFactory {
   public static Command create(Request request) {
      String command = request.getParameter("command");
      if (command == null)
         return null;
      if (command.equals("return"))
         return new ReturnCommand(request);
      if (command.equals("addPatron"))
         return new AddPatronCommand(request);
      return null;
   }
}

Writing tests for CommandFactory is easy (Listing 9)!

Listing 9. CommandFactoryTest.

public class CommandFactoryTest {
   private Request request;

   @Before
   public void initialize() {
      request = new Request();
   }

   @Test
   public void createValidCommands() {
      assertCommandType("return", ReturnCommand.class);
      assertCommandType("addPatron", AddPatronCommand.class);
   }

   @Test
   public void createWithNoCommandParameter() {
      assertNull(CommandFactory.create(request));
   }

   @Test
   public void createWithBadCommandParameter() {
      request.setParameter("command", "badCommand");
      assertNull(CommandFactory.create(request));
   }

   private void assertCommandType(String commandArgument,
                                  Class commandClass) {
      request.setParameter("command", commandArgument);
      assertEquals(commandClass,
                   CommandFactory.create(request).getClass());
   }
}

I now can change code in the service method to take advantage of the command factory (see Listing 10).

Listing 10. Incremental refactoring.

public void service(Request request) {
   String command = request.getParameter("command");
   if (command.equals("checkout")) {
      ...
   } else if (command.equals("newBranch")) {
      ...
   } else if (command.equals("addBook")) {
      ...
   } else {
      libraryCommand = CommandFactory.create(request);
      libraryCommand.execute();
    }
}

Ultimately, I can refactor all the code in the service method to use the CommandFactory. Here's what it looks like after moving all chunks of service code out to the command framework:

public void service(Request request) {
   Command command = CommandFactory.create(request);
   command.execute();
}

Not bad! At this point, I want enough unit test coverage to give me the confidence that the service method uses the factory to execute and execute a command. I don't need to re-test each command class. Instead, I only need to prove that the execute method gets called for the created command.

I could choose one representative command and verify that it got executed end-to-end. Or I could introduce a "dummy" Command implementation that did nothing but report back that it got executed:

public class NullCommand implements Command {
   public static boolean wasExecuted = false;
   public void execute() {
      wasExecuted = true;
   }
}

Along with this change, I alter the definition of the CommandFactory to never return null, instead returning a NullCommand object (see Listing 11).

Listing 11. CommandFactoryTest, revised.

public class CommandFactoryTest {
   private Request request;

   @Before
   public void initialize() {
      request = new Request();
   }

   @Test
   public void createValidCommands() {
      assertCommandType("return", ReturnCommand.class);
      assertCommandType("addPatron", AddPatronCommand.class);
   }

   @Test
   public void createWithNoCommandParameter() {
      assertEquals(NullCommand.class,
                   CommandFactory.create(request).getClass());
   }

   @Test
   public void createWithBadCommandParameter() {
      assertCommandType("badComment", NullCommand.class);
   }

   @Test
   public void createDummy() {
      assertCommandType("dummy", NullCommand.class);
   }

   private void assertCommandType(String commandArgument,
                                  Class commandClass) {
      request.setParameter("command", commandArgument);
      assertEquals(commandClass, CommandFactory.create(request)
            .getClass());
   }
}

The corresponding CommandFactory implementation is shown in Listing 12.

Listing 12. CommandFactory, revised.

public class CommandFactory {
   public static Command create(Request request) {
      String command = request.getParameter("command");
      if (command != null) {
         if (command.equals("return"))
            return new ReturnCommand(request);
         if (command.equals("addPatron"))
            return new AddPatronCommand(request);
      }
      return new NullCommand();
   }
}

The changes to CommandFactory ensure that it cannot possibly return null. My test for service is very simple:

@Test
public void service() {
   NullCommand.wasExecuted = false;
   LibrarySystem system = new LibrarySystem();
   Request request = new Request();
   request.setParameter("command", "dummy");
   system.service(request);
   assertTrue(NullCommand.wasExecuted);
}

Yes, this introduces a NullCommand class into production. It does no harm there, and otherwise it's a small price to pay for the value provided by better test coverage.

It's always possible to slowly refactor toward a better design. The improved version of the code demonstrates the Command Pattern, one of the more useful recognized software design patterns. In this case, your need to test proved to be in alignment with good design concepts!

About the Author

Jeff Langr is a veteran software developer with a score and more years of experience. He's authored two books and dozens of published articles on software development, including Agile Java: Crafting Code With Test-Driven Development (Prentice Hall) in 2005. You can find out more about Jeff at his site, http://langrsoft.com, or you can contact him directly at jeff@langrsoft.com.



Page 2 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel