Quite recently I had to implement a command pattern in a project I was working on. I noticed how much cleaner it was when using lambda’s and a static interface method compared to using the 'old' pre-8 way of providing either concrete or anonymous interface implementations for all the commands. I just wanted to share my experiences with yet another use of these java 8 features to you.
A repo with runnable examples can be found here.
Command Pattern
The command pattern is a pattern you quite often use when you have to read some information from a stream (network, file) and have to 'apply' this to our object tree. Examples of these patterns are handling chat messages, parsing log files and processing game updates.
A 'shortest route' approach would be a parser class that would contain the parsing of the information as well as the logic of applying that information. But this would tightly couple all this logic into one single monster object.
The command pattern splits the two: command instances have all the information they need to update your model and only contain the logic they need to 'work' whenever they are executed. This separates pieces into managable modules. The downside of the pre-java-8 approach is that you simply end up with a lot of concrete or anonymous implementations. So what’s the Command’s interface like? It’s really simple:
public interface Command {
public void apply(T reciever);
}
Our implementation
In this example I will use a mutable AtomicInteger object as the model (reciever) on which I 'apply' the commands. We’re doing simple setting of values, additions and substractions with one 'complex' command that reverses the stored number:
public interface Command {
Command VOID_COMMAND = i -> {};
void apply(AtomicInteger model);
static Command parse(String cmd) {
if(cmd == null || cmd.trim().equals("")) {
return VOID_COMMAND;
}
String[] parts = cmd.split(" ");
switch(parts[0]) {
case "add":
return i -> i.addAndGet(Integer.parseInt(parts[1]));
case "sub":
return i -> i.addAndGet(-Integer.parseInt(parts[1]));
case "set":
return i -> i.set(Integer.parseInt(parts[1]));
case "rev":
return new ReverseCommand();
default:
return VOID_COMMAND;
}
}
}
As you can see here we provide both the function interface method (apply()) and a 'parse' method that parses our input strings into commands. If you would want to do it this way in a real life scenario is up to you: this mainly shows the usefulness of lambda’s, static members and static methods in interfaces. In a more complex world you might want to move the parsing into a dedicated parser.
As you can see here we return simple anonymous implementations of our commands in all cases except the 'reverse' command: this one has, because it’s rather complex, it’s own implementation. We also return a simple VOID command that doesn’t do anything for any invalid commands. It’s up to you if you want to throw an exception or log a message instead.
Usage is simple and an example is provider in a test method:
//Our model is represented by a mutable integer. In reality this would
//typically be a more complex object tree.
AtomicInteger model = new AtomicInteger(0);
//Create a few reusable commands.
Command add10 = Command.parse("add 10");
Command set20 = Command.parse("set 20");
Command sub30 = Command.parse("sub 30");
assertThat(model.get(), equalTo(0));
add10.apply(model);
add10.apply(model);
assertThat(model.get(), equalTo(20));
sub30.apply(model);
assertThat(model.get(), equalTo(-10));
set20.apply(model);
assertThat(model.get(), equalTo(20));
//Commands can also be used in a more fluent manner when reusability
//is not a concern.
Command.parse("set 1234").apply(model);
assertThat(model.get(), equalTo(1234));
//Nonsensical commands return a 'void' command; it doesn't do anything.
Command.parse(null).apply(model);
Command.parse("").apply(model);
Command.parse("foo").apply(model);
assertThat(model.get(), equalTo(1234));
//More complex commands, as opposed to the simple add/set/substract ones
//have implementations. These are hidden and work exactly the same way
//as the anonymous ones.
Command.parse("rev").apply(model);
assertThat(model.get(), equalTo(4321));
This concludes this little presentation. I am really enthusiastic about the changes in Java 8 and how useful they are in real life situations and how well they tie in with our favorite design patterns. I hope I managed to convey some of this enthusiasm!