我正在开发一个使用 Java 的小型 UML 编辑器项目,这是我几个月前开始的。几周后,我得到了一个 UML 类图编辑器的工作副本。
但是现在,我完全重新设计它以支持其他类型的图表,例如序列、状态、类等。这是通过实现一个图形构建框架来完成的(Cay Horstmann 在该主题上的工作给我很大启发紫罗兰色 UML 编辑器)。
重新设计进展顺利,直到我的一位朋友告诉我,我忘记在项目中添加 Do/Undo 功能,在我看来,这是至关重要的。
想起面向对象的设计课程,我立刻想到了 Memento 和 Command 模式。
这是交易。我有一个抽象类 AbstractDiagram,它包含两个 ArrayList:一个用于存储节点(在我的项目中称为 Elements),另一个用于存储边缘(在我的项目中称为 Links)。该图可能会保留一堆可以撤消/重做的命令。很标准。
如何有效地执行这些命令?比如说,我想移动一个节点(该节点将是一个名为 INode 的接口类型,并且会有从它派生的具体节点(ClassNode、InterfaceNode、NoteNode 等))。
位置信息作为属性保存在节点中,因此通过修改节点本身中的属性,状态会改变。刷新显示时,节点将移动。这是模式的 Memento 部分(我认为),不同之处在于对象是状态本身。
此外,如果我保留原始节点的克隆(在它移动之前),我可以回到它的旧版本。相同的技术适用于节点中包含的信息(类或接口名称、注释节点的文本、属性名称等)。
问题是,在图中,如何在撤消/重做操作时将节点替换为其克隆?如果我克隆图表引用的原始对象(在节点列表中),则克隆不是图表中的引用,唯一指向的是命令本身!我是否应该在图中包含根据 ID 查找节点的机制(例如),以便我可以在图中用其克隆替换节点(反之亦然)?是否由 Memento 和 Command 模式来做到这一点?链接呢?它们也应该是可移动的,但我不想只为链接创建一个命令(一个只为节点创建一个命令),我应该能够根据命令对象的类型修改正确的列表(节点或链接)指的是。
你将如何进行?简而言之,我无法以命令/备忘录模式表示对象的状态,以便可以有效地恢复它并在图表列表中恢复原始对象,具体取决于对象类型(节点或链接)。
非常感谢!
纪尧姆。
PS:如果我不清楚,请告诉我,我会澄清我的信息(一如既往!)。
编辑
这是我在发布此问题之前开始实施的实际解决方案。
首先,我有一个 AbstractCommand 类定义如下:
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();
}
然后,使用 AbstractCommand 的具体派生来实现每种类型的命令。
所以我有一个移动对象的命令:
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;
}
}
我还有一个 MoveRemoveCommand(用于...移动或删除对象/节点)。如果我使用 instanceof 方法的 ID,我不必将图表传递给实际的节点或链接,以便它可以将自己从图表中移除(我认为这是一个坏主意)。
AbstractDiagram 图;可添加对象;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;
}
}
}
最后,我有一个 ModificationCommand,用于修改节点或链接的信息(类名等)。这可能在将来与 MoveCommand 合并。这个类暂时是空的。我可能会使用一种机制来确定修改后的对象是节点还是边(通过 instanceof 或 ID 中的特殊表示)来执行 ID 操作。
这是一个好的解决方案吗?