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:
- 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 anAction
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. - 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.
- 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.
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.
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.
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:
- Define the subcommand interface
- 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.
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
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.