Newsletter sign-up

Sign up for our technology specific newsletters.

Enterprise Java
View all newsletters

Email Address:

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.

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