JFC actions

Use the powerful Command pattern with the JFC Action interface to build a reusable GUI-command library

The JFC Action interface provides a mechanism to detach application logic from GUI code. Unfortunately, Action implementations must usually be rewritten for each GUI, even if the action performs the same high-level operation. Fortunately though, through the careful use of interfaces and the Command pattern, it is possible to develop a library of reusable actions!

In this article, we will explore the Command pattern and JFC Actions. Using the JFC Action and Java interfaces, we will develop a library of commands that can be found in most applications. These commands include the File commands: New, Open, Save, Save As, Print, Close, and Exit; as well as the Edit commands: Cut, Copy, Paste, Delete, Select All, and Deselect all.

Actions 101: A primer

Java's JFC library provides an Action type. Actions are not visible components. Instead, an action encapsulates some GUI command and can take on a number of different graphical representations. In a typical GUI, an action can work behind the scenes to provide the logic for a button, menu item, or toolbar item. When created from an action, the component uses the action to obtain its string label, tooltip, icon, state, and

ActionListener

callback. Let's look at the

javax.swing.Action

interface:

public interface Action extends ActionListener
{
  public static final String DEFAULT = "Default";
  public static final String NAME = "Name";
  public static final String SHORT_DESCRIPTION = "ShortDescription";
  public static final String LONG_DESCRIPTION = "LongDescription";
  public static final String SMALL_ICON = "SmallIcon";
  public void addPropertyChangeListener(PropertyChangeListener listener);
  public Object getValue(String key);
  public boolean isEnabled();
  public void putValue(String key, Object value);
  public void removePropertyChangeListener(PropertyChangeListener listener);
  public void setEnabled(boolean b);
}

The class javax.swing.AbstractAction provides a basic action implementation. To create an action, we simply extend AbstractAction, provide the display information, and implement the action logic by defining the actionPerformed() method. Unless there is a compelling reason for you to reimplement the Action interface yourself, AbstractAction should suffice as an inheritable in most cases.

On the surface, actions may not seem very useful. However, they provide many benefits, which are listed below:

  1. Actions decentralize GUI logic by removing it from the GUI code entirely, thus simplifying the structure of GUI code immensely. Normally, one large actionPerformed() method contains all of the switch logic for each button and menu item in the window. In contrast, with actions we can avoid this switch nonsense by assigning an Action instance to each GUI construct. This is an approach that offers an elegant object-oriented alternative to the switch/case mess that generally exists in Java GUI code.
  2. Actions are not components. As a result, a single action may be shared among multiple GUI components. So, for example, a single action may have a menu representation and simultaneously appear in a toolbar. Instead of having to write the logic for the menu in one place and separate logic for the toolbar equivalent in another place, all logic resides in one centralized location.
  3. Actions simplify the creation of GUI constructs such as the menu item or toolbar button. To create a menu item or button, simply pass the action into the component's constructor. Once created this way, there is nothing else to do to wire the component to its callback, set its text label, or set its state -- the action handles all of these details.

Unfortunately, actions do not come without a price: the logic of the command is often mired inside the action itself. Actions tempt us to place all of the logic inside of the actionPerformed() method. Unfortunately, whenever something changes, you'll need to rewrite your actions. As a result, you can't easily take an action to another GUI. Instead, you are forced to constantly rewrite or extend the action portion of your code, even if these actions present the same command.

The Command pattern

The powerful Command pattern decouples a request from the requestor. The Command pattern succeeds in decoupling the request by turning the request logic into an object in its own right. So what is a request? A request is any high-level operation. In a GUI, cut and paste are requests, for example. When you choose the cut menu item, you send a request to the GUI to cut something. Instead of implementing the request logic in the calling object -- here a menu item -- the Command pattern requires that the request's implementation be encapsulated in its very own command object. The command contains everything it needs to know to carry out the request. Figure 1 shows the Command pattern's approach to command creation.

Figure 1. Commands

In Figure 1, all command implementations extend the common CommandIF interface, which defines one method:

public void execute();

The calling object uses the execute() method to kick off and run the command. The caller doesn't need to know anything about what is going on inside the command. Instead, all of those details are nicely encapsulated. The caller is just concerned with the object implementing the CommandIF interface. The caller trusts that the implementer will do its job correctly and doesn't really care what that job is or who is affected. When designed around the Command pattern, the calling object simply knows how to manipulate commands.

Decoupled command logic provides many benefits. From a purely object-oriented point of view, we've now turned application logic into objects. This step by itself is fairly exciting: application logic can now enjoy the benefits of solid object-oriented design. Right away, we see that command objects provide a clear division of labor between classes. Instead of implementing all of the application logic in one or two large classes, command objects enable us to divide functionality among multiple classes. The command-object approach embraces encapsulation by turning the logic of an application into a black box. The application doesn't need to know anything about the encapsulated feature in order to use it. Before, an application knew everything about its operations. Now, the application can treat operations just as it would treat any other object.

From a development point of view, decoupled commands allow one developer to code a GUI while another developer works in parallel implementing the GUI's commands. It also means that a user or developer can upgrade an application by swapping in new, more robust command implementations as they become available. Because commands are now objects, these new implementations are useable without having to rewrite any application code -- a nice way to iterate during development or to provide incremental product upgrades. Following the Command pattern also makes it easier to deploy other difficult-to- implement functionality such as command queuing, undo/redo, logging, and transactions. Because we no longer embed these commands, it is easier to track command execution since the application can treat all commands in the same generic manner. To the application, the command to print looks the same as the command to cut text. The Command pattern allows the developer to add generic functionality that will automatically extend to all commands in the application as they are added.

Reusable actions: Command actions

By employing the Command pattern we can further decouple the command's logic from the action. By decoupling the command from the action, we can swap command objects into the action without having to change the action. We can therefore move the burden of rewrite to where it belongs -- the command object itself.

Without an example, it's difficult to see how this is necessarily a good idea. Think of it this way: JFC provides a generic button called JButton. To get JButton to do what we need it to do in the GUI, we write an ActionListener and register that listener with the button instance. It would be ridiculous to extend JButton and provide application-specific logic in that subclass. It is just as ridiculous to have to do this with an Action implementation. Some may argue that an action is no more than an ActionListener anyway. Technically, they would be right. If that is your view of Action, fine. But why not take it one step further and create reusable actions? Instead of extending and rewriting our actions, we can create an Action implementation that conforms to the Command pattern.

The idea behind a reusable action is simple: we don't want to ever rewrite an action! Once we've defined an action for a command such as Cut, we never, ever want to have to rewrite that Cut action.

Unfortunately, you can't avoid doing some work. You can't write an action that will logically know how to perform the cut command in every situation. Instead, you can write an Action implementation that knows how to execute a command. In the case of Cut, this action would simply execute a Cut command. So when you move your Cut action to a new GUI, you will need to rewrite only the Cut command.

Figure 2 below models this idea. The CommandAction holds on to a CommandIF implementation. When the actionPerformed() method is called, the action simply calls the execute() method on the command.

Figure 2. CommandAction

The CommandAction class, detailed below, is fairly straightforward. The constructors take a command, label, and an optional icon. The actionPerformed() method simply takes the command and executes it when the action becomes activated. Now, you can plug commands into the CommandAction as needed without having to rewrite the action:

public class CommandAction extends AbstractAction
{
     // the encapsulated command
     protected CommandIF command;
     // create with a label, icon, and command
     public CommandAction(CommandIF command, String name, Icon icon)
     {
          super(name, icon);
          setCommand(command);
     }
     // create with a label and command
     public CommandAction(CommandIF command, String name)
     {
          super(name);
          setCommand(command);
     }
     // the actionPerformed implementation, simply calls command.execute()
     public void actionPerformed(ActionEvent e)
     {
          getCommand().execute(); // kick off the command
     }
     // internal setter and getters
     protected final void setCommand(CommandIF newValue)
     {
          this.command = newValue;
     }
     protected final CommandIF getCommand()
     {
          return command;
     }
}

Really, the CommandAction is all we need for a fully pluggable, Command pattern-oriented, Action implementation. However, we can do a few more things to make the CommandAction class more useful, as we'll see next.

CommandAction subclasses

The command approach relies heavily on Java interfaces, which are best used when change is easily foreseen. Obviously, we will want to create new command types as they are needed. Thus, the

CommandAction

will experience constant change. By defining command as an interface, we can create new command objects that implement

CommandIF

. As long as our commands implement this interface, we can plug them into the

CommandAction

. Furthermore, because our command definition is an interface, we are not stuck with a rigidly defined class hierarchy. Instead, unrelated objects can plug into the

CommandAction

as long as they conform to the

CommandIF

interface.

We can go one step further, and employ more interfaces and develop a library of specialized CommandAction subclasses. Let's take a look at a possible command interface that extends CommandIF:

public interface EditCommandIF extends CommandIF {}

Any command appearing in the Edit menu must extend the EditCommandIF interface. One of these subcommands might be the cut command, as seen below:

public interface CutCommandIF extends EditCommandIF {}

The CutCommandIF interface extends the EditCommandIF interface, which in turn extends the original CommandIF interface. Neither of the new interfaces adds any new methods or member definitions. Instead, class implementations may use the CutCommandIF interface to designate themselves as a certain type of command. Obviously, generic code can still treat new interfaces as if they are just CommandIF interfaces. However, a time may come where there is a need to filter commands.

Let's take a look at a class -- CutAction -- that extends the CommandAction and takes advantage of the refined command definition:

public class CutAction extends CommandAction
{
  public final static String icon = "cut_action.gif";
  public final static String text = "Cut";
  public CutAction(CutCommandIF command)
  {
    super(command, text, new 
ImageIcon(ClassLoader.getSystemResource(icon)));
  }
}

The CutAction class extends our CommandAction class. However, I have redefined the constructor to accept only commands of type CutCommandIF. Notice that I have not redefined the actionPerformed() method. Instead, actionPerformed() still treats the command as if it were just a plain old command; it does not care that it is really a cut command.

Figure 3. Application commands

However, the CutAction action hardcodes a text label and icon name. It makes no sense to feed this action a paste command. If you did feed it a different command type, you would have the wrong label and icon. By subclassing the command interface and defining new command types, we can write convenience actions that will take only the proper type of command. While CommandAction provides everything that we need for pluggable actions, it is nice to have specialized actions that know their label and icon. This way we can build a new command and plug it into a smarter action that already knows the command's icon and label, which speeds up development since you don't have to create icons and then put it all together into an action. Once you defined these specialized actions, they can be used across GUIs. If we simply used CommandAction, we would have to recreate the cut action in each GUI.

So, to create specialized actions, we need to:

  1. Define the subcommand interface
  2. Create the specialized action class

Both steps are fairly simple. Step 1 entails extending the CommandIF interface or some other CommandIF sub-interface. Step 2 entails extending the CommandAction and providing a new constructor, along with a label and an optional icon.

Figure 4. File commands

By following this recipe, it is possible to create a reusable library of common GUI actions, including those options normally found in the File and Edit menus. Instead of reproducing all of that code here, I've created a reusable library of commands. Figures 3, 4, and 5 model the library's object structure.

The commands include:

  • File: New, Open, Save, Save As, Print, Close, and Exit
  • Edit: Cut, Copy, Paste, Delete, Select All, and Deselect All
Figure 5. Edit commands

Each command interface has a corresponding action class. (See Resources to download the action library.)

Conclusion

Object-oriented programming techniques bring numerous benefits to software design and development. The Command pattern simply extends object-oriented design principles to include request logic. By combining the Command pattern and the JFC Action, we can easily bring the benefits of decoupled application logic to the Java GUI. Once it is designed to take advantage of the Command pattern, it is easy to add sophisticated functionality to almost any Java GUI.

Tony Sintes is a senior consultant specializing in telecommunications consulting at ObjectWave Corporation. (http://www.objectwave.com/) Tony is also the JavaWorld Q&A expert. Outside all of this "computer techno mumbo jumbo," Tony enjoys spending time with his wife Amy and tending to his marine aquariums.

Learn more about this topic

  • Source code resources
  • GUI Resources
  • The Pattern Languages of Program Design series
  • More pattern resources