I am working on a small UML editor project in Java that I started a couple of months ago. A few weeks later I received a working copy of the UML class diagram editor.
But now I am completely redesigning it to support other types of diagrams, such as sequence, state, class, etc. This is done by implementing the graph construction structure (I am very inspired by the work of Kay Horstman on using the Violet UML editor).
The redesign went smoothly until one of my friends told me that I forgot to add Do / Undo functionality to the project, which, in my opinion, is vital.
Remembering the object-oriented design courses, I immediately thought of the Memento and Command patterns.
Here's the deal. I have an AbstractDiagram abstract class containing two ArrayLists: one for storing nodes (called Elements in my project), and the other for storing Edges (called links in my projects). The chart is likely to contain a stack of commands that can be undone / undone. Pretty standard.
How can I effectively execute these commands? Say, for example, that I want to move a node (node ββwill be an interface type named INode, and specific nodes (ClassNode, InterfaceNode, NoteNode, etc.) will be obtained from it).
Location information is stored as an attribute in the node, so by changing this attribute in the node itself, the state changes. When the display is updated, the node will be moved. This is part of the Memento template (I think), with the difference that the object is the state itself.
Also, if I save a clone of the original node (before moving it), I can revert to its old version. The same method is used for information contained in a node (class or interface name, node text, node name, attribute name, etc.).
The thing is, how to replace the node with your clone on the undo / redo operation in the diagram? If I clone the source object referenced by the diagram (being in the node list), the clone is not a link to the diagram, and the only thing that indicates the command itself! Shoud do I enable the mechanisms on the diagram to find the node according to the identifier (for example), so can I replace the node on the diagram with my clone (and vice versa)? Do you need Memento and Command templates for this? What about links? They must also be movable, but I do not want to create a command only for links (and only for nodes), and I should be able to change the correct list (nodes or links) according to the type of object. The command refers to.
What will you do? In short, I had a problem with representing the state of the object in the command / memento template so that it can be restored efficiently and the original object is restored in the diagram list depending on the type of object (node ββor link).
Thanks a lot!
Guillaume.
PS: If I donβt know, tell me and I will clarify my message (as always!).
Edit
Here is my actual solution that I started implementing before posting this question.
Firstly, I have an AbstractCommand class, which is defined as follows:
public abstract class AbstractCommand { public boolean blnComplete; public void setComplete(boolean complete) { this.blnComplete = complete; } public boolean isComplete() { return this.blnComplete; } public abstract void execute(); public abstract void unexecute(); }
Then each type of command is implemented using a specific AbstractCommand output.
So, I have a command to move an object:
public class MoveCommand extends AbstractCommand { Moveable movingObject; Point2D startPos; Point2D endPos; public MoveCommand(Point2D start) { this.startPos = start; } public void execute() { if(this.movingObject != null && this.endPos != null) this.movingObject.moveTo(this.endPos); } public void unexecute() { if(this.movingObject != null && this.startPos != null) this.movingObject.moveTo(this.startPos); } public void setStart(Point2D start) { this.startPos = start; } public void setEnd(Point2D end) { this.endPos = end; } }
I also have MoveRemoveCommand (for ... moving or deleting an object / node). If I use the instanceof method identifier, I donβt need to pass the chart to the actual node or link so that it can be removed from the chart (which I think is a bad idea).
Abstract chart diagram; The added object; Type AddRemoveType;
@SuppressWarnings("unused") private AddRemoveCommand() {} public AddRemoveCommand(AbstractDiagram diagram, Addable obj, AddRemoveType type) { this.diagram = diagram; this.obj = obj; this.type = type; } public void execute() { if(obj != null && diagram != null) { switch(type) { case ADD: this.obj.addToDiagram(diagram); break; case REMOVE: this.obj.removeFromDiagram(diagram); break; } } } public void unexecute() { if(obj != null && diagram != null) { switch(type) { case ADD: this.obj.removeFromDiagram(diagram); break; case REMOVE: this.obj.addToDiagram(diagram); break; } } }
Finally, I have a ModificationCommand, which is used to change the information about a node or link (class name, etc.). It may be combined in the future with MoveCommand. While this class is empty. I will probably make an identifier with a mechanism to determine if the modified object is a node or an edge (via instanceof or a special notation in the ID).
This is a good decision?