views:

617

answers:

1

Hello,

I'm working on a small UML editor project, in Java, that I started a couple of months ago. After a few weeks, I got a working copy for a UML class diagram editor.

But now, I'm redesigning it completely to support other types of diagrams, such a sequence, state, class, etc. This is done by implementing a graph construction framework (I'm greatly inspired by Cay Horstmann work on the subject with the Violet UML editor).

Redesign was going smoothly until one of my friends told me that I forgot to add a Do/Undo functionnality to the project, which, in my opinion, is vital.

Remembering object oriented design courses, I immediately thought of Memento and Command pattern.

Here's the deal. I have a abstract class, AbstractDiagram, that contains two ArrayLists : one for storing nodes (called Elements in my project) and the other for storing Edges (called Links in my projects). The diagram will probably keep a stack of commands that can be Undoed/Redoed. Pretty standard.

How can I execute these commands in a efficient way? Say, for example, that I want to move a node (the node will be an interface type named INode, and there will be concrete nodes derived from it (ClassNode, InterfaceNode, NoteNode, etc.)).

The position information is held as an attribute in the node, so by modying that attribute in the node itself, the state is changed. When the display will be refreshed, the node will have moved. This is the Memento part of the pattern (I think), with the difference that the object is the state itself.

Moreover, if I keep a clone of the original node (before it moved), I can get back to its old version. The same technique applies for the information contained in the node (the class or interface name, the text for a note node, the attributes name, and so on).

The thing is, how do I replace, in the diagram, the node with its clone upon undo/redo operation? If I clone the original object that is referenced by the diagram (being in the node list), the clone isn't reference in the diagram, and the only thing that points to is the Command itself! Shoud I include mechanisms in the diagram for finding a node according to an ID (for example) so I can replace, in the diagram, the node by its clone (and vice-versa) ? Is it up to the Memento and Command patterns to do that ? What about links? They should be movable too but I don't want to create a command just for links (and one just for nodes), and I should be able to modify the right list (nodes or links) according to the type of the object the command is referring to.

How would you proceed? In short, I am having trouble representing the state of an object in a command/memento pattern so that it can be efficiently recovered and the original object restored in the diagram list, and depending on the object type (node or link).

Thanks a lot!

Guillaume.

P.S.: if I'm not clear, tell me and I will clarify my message (as always!).

Edit

Here's my actual solution, that I started implementing before posting this question.

First, I have an AbstractCommand class defined as follow :

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 concrete derivation of AbstractCommand.

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 a MoveRemoveCommand (to... move or remove an object/node). If I use the ID of instanceof method, I don't have to pass the diagram to the actual node or link so that it can remove itself from the diagram (which is a bad idea I think).

AbstractDiagram diagram; Addable obj; AddRemoveType type;

@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 modify the info of a node or link (class name, etc.). This may be merged in the future with the MoveCommand. This class is empty for now. I will probably do the ID thing with a mechanism to determine if the modified object is a node or an edge (via instanceof or a special denotion in the ID).

Is this is a good solution?

+4  A: 

I think you just need to decompose your problem into smaller ones.

First problem: Q: How to represent the steps in your app with the memento/command pattern? First off, I have no idea exactly how your app works but hopefully you will see where I am going with this. Say I want to place a ClassNode on the diagram that with the following properties

{ width:100, height:50, position:(10,25), content:"Am I certain?", edge-connections:null}

That would be wrapped up as a command object. Say that goes to a DiagramController. Then the diagram controller's responsibility can be to record that command (push onto a stack would be my bet) and pass the command to a DiagramBuilder for example. The DiagramBuilder would actually be responsible for updating the display.

DiagramController
{
  public DiagramController(diagramBuilder:DiagramBuilder)
  {
    this._diagramBuilder = diagramBuilder;
    this._commandStack = new Stack();
  }

  public void Add(node:ConditionalNode)
  {
    this._commandStack.push(node);
    this._diagramBuilder.Draw(node);
  }

  public void Undo()
  {
    var node = this._commandStack.pop();
    this._diagramBuilderUndraw(node);
  }
}

Some thing like that should do it and of course there will be plenty of details to sort out. By the way, the more properties your nodes have the more detailed Undraw is going to have to be.

Using an id to link the command in your stack to the element drawn might be a good idea. That might look like this:

DiagramController
{
  public DiagramController(diagramBuilder:DiagramBuilder)
  {
    this._diagramBuilder = diagramBuilder;
    this._commandStack = new Stack();
  }

  public void Add(node:ConditionalNode)
  {
    string graphicalRefId = this._diagramBuilder.Draw(node);
    var nodePair = new KeyValuePair<string, ConditionalNode> (graphicalRefId, node);
    this._commandStack.push(nodePair);
  }

  public void Undo()
  {
    var nodePair = this._commandStack.pop();
    this._diagramBuilderUndraw(nodePair.Key);
  }
}

At this point you don't absolutely have to have the object since you have the ID but it will be helpful should you decide to also implement redo functionality. A good way to generate the id for your nodes would be to implement a hashcode method for them except for the fact that you wouldn't be guaranteed not to duplicate your nodes in such a way that would cause the hash code to be identical.

The next part of the problem is within your DiagramBuilder because you're trying to figure out how the heck to deal with these commands. For that all I can say is to really just ensure you can create an inverse action for each type of component you can add. To handle the delinking you can look at the edge-connection property (links in your code I think) and notify each of the edge-connections that they are to disconnect from the specific node. I would assume that on disconnection they could redraw themselves appropriately.

To kinda summarize, I would recommend not keeping a reference to your nodes in the stack but instead just a kind of token that represents a given node's state at that point. This will allow you to represent the same node in your undo stack at multiple places without it referring to the same object.

Post if you've got Q's. This is a complex issue.

Justin Bozonier