As a preface, this is my first library, and somewhat embarrassingly, my first blog post π±. Any feedback would be greatly appreciated! All gotta start somewhere right?
While procrastinating over my degree, I realised that my final project contained some useful command delegation code that could make for a nice little library. With no further ado, let me introduce...
re-agent
For the readme (it's down below as well) and code, checkout:
A Java command delegation & undo-redo library.
Features
- Undo & Redo
- Loosely coupled
Command Pattern
- Listener support
Installation
The current release version can be found on the maven central repository. To use it add the following dependency to your pom.xml
:
<dependency>
<groupId>com.logdyn</groupId>
<artifactId>re-agent</artifactId>
<version>1.2</version>
</dependency>
The release can also be found on the GitHub releases.
How to Use
Commands
A realization of the Command
interface is used to trigger an event in the subscribed Executor
. Command
's do not specify execution behaviour, rather they are used to trigger an event and to capture any information that will be needed for that event.
class ExampleCommand implements UndoableCommand {
@Override
public String getName() {
return "Example Undoable Command";
}
}
There are also UndoableCommand
's, which can beβ¦
Background
This library started out as the undo / redo controller for another project, and was adapted by myself and my coder-in-crime Matt as part of our collaborative work on GitHub.
The product had to do three things:
- Make command delegation easy
- Make undo/redo easy
- Be as loosely coupled as possible
So what do I mean by command delegation?
Say you have a program that opens a file, and provides you with two different editor windows. Each editor window will obviously have it's own controller, but the file operations can likely be shared between them. In our case, the editors don't particularly care how the file is opened, just that they don't get forgotten... editor.populate(text)
will do them nicely.
A FileMenuController
will know when you want to open a file, but we want our editors to know too, but we don't necessarily want the controller to know about the editors. This is where our command delegator comes in.
The editors will subscribe to the command delegator, saying that they want to know about certain events, which in this case is a FileOpenCommand
. When the file controller opens a file it will publish a FileOpenCommand
to the delegator, with whatever information necessary to open the file. The command delegator will forward this command on to the editors, and they will worry about the execution of the command.
Ta-da! Successful delegation π but we're not done yet...
Undo & Redo
As anyone who has implemented an undo/redo system before will know, possibly the trickiest aspect is reverting state. To be able to undo an action, you need methods that act in a RESTful manner (behave the same regardless of state) or you need to store the state you want to go back to.
Being the magnanimous developers we are, we decided we didn't care, and you should be able to do what you want. Yay, freedom! In this vein, the process of undo/redo uses four entities: delegator, publisher, command, and executor. Commands may or may not contain state, but they are always fairly tightly coupled to their executor
- Delegator: handles executor subscription, and passes around calls for
DO
,UNDO
, andREDO
to an executor of a command - Publisher: anything that sends a command to the delegator
- Command: basically a data packet, might have utility methods to be used by the executor
- Executor: Controls how commands are 'done', with the option of implementing undoing and redoing
So how do I install it?
Just add it as a dependency on Maven! (The always brilliant Jenkov tutorials have you covered)
<dependency>
<groupId>com.logdyn</groupId>
<artifactId>re-agent</artifactId>
<version>1.2</version>
</dependency>
So how do I use it?
I'm lazy so this is coming straight out of the readme, but I'm eager for suggestions to improve it π
Commands
A realization of the Command
interface is used to trigger an event in the subscribed Executor
. Command
's do not specify execution behaviour, rather they are used to trigger an event and to capture any information that will be needed for that event.
class ExampleCommand implements UndoableCommand {
@Override
public String getName() {
return "Example Undoable Command";
}
}
There are also UndoableCommand
's, which can be executed by any Executor
, although if you want to make use of undo/redo an UndoableExecutor
must be used. By default the reexecute()
method calls execute()
, although this can be overridden. An UndoableCommand
must provide data necessary for undoing an action, as well as the initial execution.
Executors
A realization of the Executor
interface is used to execute specific behaviour when a Command
has been published.
class ExampleExecutor implements UndoableExecutor<ExampleCommand> {
@Override
public void execute(ExampleCommand command) {
System.out.println("Hello, World!");
}
@Override
public void unexecute(ExampleCommand command) {
System.out.println("Goodbye, World!");
}
@Override
public void reexecute(ExampleCommand command) {
System.out.println("Hello again, World!");
}
}
Subscribing to a Command
Subscribing to a Command
requires you to specify an Executor
and the Command
that it will execute. An Executor
will execute the type of Command
it is subscribed to, or any sub-class of that Command
. As a result of this, only one Executor
of this type or sub-type of Command
.
CommandDelegator.getINSTANCE().subscribe(new ExampleExecutor(), ExampleCommand.class);
Publishing (Doing
) a Command
Publishing a Command
is as simple as passing it into the publish()
method. This will then call the execute()
method of the relevant Executor
class. The call to execute()
will be on the same thread as the call to publish
, this means that if you want to initiate a task to run in the background it must be published from the background.
If a published Command
is not undoable, it will clear the current undo history. Likewise if you have undone a Command
and a new one is published, the redo history will be cleared.
CommandDelegator.getINSTANCE().publish(new ExampleCommand());
Undo & Redo
Undo & Redo are method calls on the CommandDelegator
. This operates in the same manner as the publish()
method, it will call the unexecute()
or reexecute()
method of the relevant Executor
class.
CommandDelegator.getINSTANCE().undo();
CommandDelegator.getINSTANCE().redo();
All done!
Any questions or suggestions would be great, either here π¬, my Twitter, or email me at jake@logdyn.com
Top comments (0)