Advanced Typesafe Command Pattern using Java Generics
If you have a need to use the command pattern and want to have a standard interface but with type safe parameters, I have come up with a solution. This is one of the most interesting uses (or hacks) of generics I've ever seen or come up with and being that it took a little bit of deep thought to get worked out, I'd like to share it with you all.
- You can call the code from anywhere in the system without dependencies.
- You can bundle complicated logic into one place
- You can decouple the code that executes from the interface allowing for multiple different implementations
- You can modularize your code, allowing for a larger system that is still manageable.
What is the Command pattern? Wikipedia sums it up better than I can - http://en.wikipedia.org/wiki/Command_pattern
Some people say a true command pattern is one where the Command requires no parameters. While that may be good in some cases, most often I've found that you certainly want to pass some sort of parameters to the command, otherwise why not just use Runnable?
Of course you can just use an untypesafe interface of public Object executeCommand(String commandName, String... args) but that does not get you any compile-time checking of the arguments or the return type. It also doesn't even guarantee you got the command name right.
What I wanted to do was to make it so that if I only knew the Command, I would know exactly which parameters it took, but all on a single method interface. I didn't want to have different interfaces for different commands because I was looking to have all invocations of commands be done from any subclass of MyBaseClass. For my example, we're going to have a Command be "FileChooserCommand", which will take a starting directory and filename mask as parameters and return a File. Here is how I wanted my class that is invoking the Command to work:
public class MyClass extends MyBaseClass {
public void doStuff() {
System.out.println("Watch me invoke the command");
File chosenFile = executeCommand(Command.CHOOSE_FILE, new ChooseFileCommandParameters("c:\", "*.txt"));
System.out.println("The file you chose is: " + chosenFile);
}
}
Can you see the simplicity in this interface? Of course there are other ways to do this, but I chose this because I can standardize every conceivable command with it. What's really cool about it is that for every different command, you are guaranteed to have the correct parameter type passed in compile-time. It may seem like a trivial thing to get at first, but believe me, it's not. Java lets you define interfaces and lets you parameterize them, but it doesn't let you have the second argument and return type both be dependent on the first argument. Did you understand that? This makes it so that both the return type and second argument change compile-time based on the first argument. I'm trying to really push this idea because it's useful in other cases and not that easy to do properly.
Here are the key pieces of code you will need to make this happen:
/** the command class defines the interface for a command and has the available commands listed out in my
* implementation. The important thing is the interface itself but the Command constants can be located in any
* class or classes.
*/
public class Command<ParamType, ReturnType> {
public static final Command<ChooseFileCommandParams, File> CHOOSE_FILE = new Command<ChooseFileCommandParams, File>(
"Choose File");
public static final Command<AnotherCommandParams, String> ANOTHER_COMMAND = new Command<AnotherCommandParams, String>(
"Another Command");
private String name;
private Command(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
/**
* This is the interface for handling the commands that are executed. Implement this using the correct parameter
* type and return type to have code that handles the command.
*/
public interface CommandHandler<ParamType, ReturnType> {
public ReturnType execute(ParamType params) throws Exception;
}
/**
* Used to Bind Commands to CommandHandlers with parameterized types
*
* The point of this class is to enforce the 1-1 relationship between a command's param/return types and the handler's
* param/return types
*
*/
public class CommandHandlerBinding<ParamType, ReturnType> {
private Command<ParamType, ReturnType> command;
private CommandHandler<ParamType, ReturnType> handler;
public CommandHandlerBinding(Command<ParamType, ReturnType> command, CommandHandler<ParamType, ReturnType> handler) {
this.command = command;
this.handler = handler;
}
public Command<ParamType, ReturnType> getCommand() {
return command;
}
public CommandHandler<ParamType, ReturnType> getHandler() {
return handler;
}
}
/**
* base class - I don't do anything with it but I'm sure people would.
*/
public abstract class CommandParameters {
}
/**
* Parameters for the file chooser command
*/
public class ChooseFileCommandParameters extends CommandParameters {
private String path;
private String filter;
public ChooseFileCommandParameters (String path, String filter) {
this.path = path;
this.filter = filter;
}
public String getPath() {
return path;
}
public String getFilter() {
return filter;
}
}
/**
* The ChooseFileCommandHandler. Performs the real work.
*/
public class ChooseFileCommandHandler implements CommandHandler<ChooseFileCommandParams, File> {
public File execute(ChooseFileCommandParams params) throws Exception {
// UI code here to set up the chooser, set look and feel, defaults, etc..
// of course this won't compile because I made up a fake implementation.
// the point is to show how the work is encapsulated and nicely parameterized.
FileChooser chooser = new FileChooser(params.getPath(), params.getFilter());
chooser.open();
File chosenFile = chooser.getSelection();
if (chosenFile == null) {
new AlertDialog("File not chosen!");
}
return chosenFile;
}
}
/**
* Handles registering command handlers (binding them to commands) and executing. I called this
* ApplicationContext but it can be any class that's initialized on application startup and accessible to any
* code that needs to execute commands. Most application frameworks have something like this.
*/
public class ApplicationContext {
private List<CommandHandlerBinding> commandHandlerBindings;
public void initializeApplication() {
registerCommandHandler(Command.CHOOSE_FILE, new ChooseFileCommandHandler());
}
public <ParamType, ReturnType> void registerCommandHandler(Command<ParamType, ReturnType> command,
CommandHandler<ParamType, ReturnType> handler) {
CommandHandler<ParamType, ReturnType> existingHandler = getCommandHandler(command);
if (existingHandler != null) {
System.out.println("Command [" + command.getName() + "] already has a handler ["
+ existingHandler.getClass().getName() + "] but is being set to [" + handler.getClass().getName()
+ "]");
}
commandHandlerBindings.add(new CommandHandlerBinding<ParamType, ReturnType>(command, handler));
}
private <ParamType, ReturnType> CommandHandler<ParamType, ReturnType> getCommandHandler(Command<ParamType, ReturnType> command) {
for (CommandHandlerBinding<ParamType, ReturnType> binding : commandHandlerBindings) {
if (binding.getCommand().getName().equals(command.getName())) {
return binding.getHandler();
}
}
return null;
}
public <ParamType, ReturnType> ReturnType executeCommand(Command<ParamType, ReturnType> command,
ParamType parameters) throws Exception {
CommandHandler<ParamType, ReturnType> handler = getCommandHandler(command);
if (handler == null) {
throw new NullPointerException("No CommandHandler registered for Command [" + command.getName() + "]");
}
return handler.execute(parameters);
}
}
/**
* The base class for anything.
*/
public abstract class MyBaseClass {
private ApplicationContext applicationContext;
public <ParamType, ReturnType> ReturnType executeCommand(Command<ParamType, ReturnType> command,
ParamType parameters) throws Exception {
return getApplicationContext().executeCommand(command, parameters);
}
private ApplicationContext getApplicationContext() {
return applicationContext;
}
}
/**
* Finally, the top of the stack. This is the class that is invoking the command. Looks simple from up
* here, doesn't it?
*/
public class MyClass extends MyBaseClass {
public void doStuff() {
System.out.println("Watch me invoke the command");
File chosenFile = executeCommand(Command.CHOOSE_FILE, new ChooseFileCommandParameters("c:\", "*.txt"));
System.out.println("The file you chose is: " + chosenFile);
}
}
There are many conceptual uses for this pattern and the way you integrate into your existing code base may vary. I know some people will object to defining the Commands as constants or having them defined in the Command interface. You don't have to do it that way. You can do it however you want. The proof here is that you can have dependent type safe parameters using generics. Try editing MyClass to have a String as the parameter. It won't compile. The idea is that your API will be very easy to use because your command pattern can ensure that programmers are passing the right parameters in and not casting the return type, which is determined by the Command itself.
In a large application, I like to have logical units defined as Modules and each module is responsible for registering its own commands with the application context. That allows for commands to come and go just like plugins. One module can depend on another module's commands. All it needs to know is the location of the Command Constant and the CommandParameters class. That's the whole high-level API.
You'll notice that most of the meat of the work here is done in ApplicationContext. That is not easy code to read, but fortunately it's not something you'll ever have to update. It handles, using the binding, the parameterization dependencies.
This whole thing could be simplified if Java were to add some syntax to allow for parameter associations. Perhaps someone from Sun will read this and we can talk about it.
Related Articles:
Object Oriented Programming Explained - Part 2 - Polymorphism
Object Oriented Programming Explained - Part 1 - The Basics
How to start writing software: Web Applications
How I became a professional software developer



Leave a comment