我正在研究Java中的一个小型UML编辑器项目,这是我几个月前开始的。几周后,我得到了一个UML类图编辑器的工作副本。

但是现在,我正在重新设计它以支持其他类型的图表,例如序列,状态,类等。这是通过实现图形构建框架来完成的(我很大程度上受到了Cay Horstmann工作的启发。主题为Violet UML编辑器。)

重新设计进展顺利,直到我的一位朋友告诉我,我忘了在项目中添加Do / Undo功能,在我看来,这是至关重要的。

记住面向对象的设计课程,我立即想到了Memento和Command模式。

这是交易。我有一个抽象类AbstractDiagram,它包含两个ArrayLists:一个用于存储节点(在我的项目中称为Elements),另一个用于存储Edges(在我的项目中称为Links)。该图可能会保留一堆可以撤消/重做的命令。很标准。

如何以有效的方式执行这些命令?比如说,我想移动一个节点(该节点将是一个名为INode的接口类型,并且将从中派生具体节点(ClassNode,InterfaceNode,NoteNode等))。

位置信息作为节点中的属性保存,因此通过在节点本身中修改该属性,状态会发生变化。当刷新显示时,节点将移动。这是模式中的Memento部分(我认为),区别在于对象是状态本身。

此外,如果我保留原始节点的克隆(在它移动之前),我可以回到它的旧版本。相同的技术适用于节点中包含的信息(类或接口名称,注释节点的文本,属性名称等)。

问题是,如何在图中用undo / redo操作替换节点及其克隆?如果我克隆图中引用的原始对象(在节点列表中),则克隆不是图中的引用,唯一指向的就是Command本身! Shoud我在图中包含了根据ID查找节点的机制(例如)所以我可以在图中用它的克隆替换节点(反之亦然)?这是由Memento和Command模式来完成的吗?链接怎么样?它们也应该是可移动的,但我不想为链接创建一个命令(并且只为节点创建一个),我应该能够根据命令对象的类型修改正确的列表(节点或链接)指的是。

你会怎么做?简而言之,我无法在命令/纪念模式中表示对象的状态,以便可以有效地恢复它并且原始对象在图表列表中恢复,并且取决于对象类型(节点或链接)。

非常感谢!

纪尧姆。

P.S。:如果我不清楚,请告诉我,我会澄清我的信息(一如既往!)。

修改

这是我的实际解决方案,我在发布此问题之前就已开始实施。

首先,我有一个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合并。这个类现在是空的。我会

有帮助吗?

解决方案

我认为您只需将问题分解为较小的问题。

第一个问题: 问:如何用memento /命令模式表示应用程序中的步骤? 首先,我不知道你的应用程序究竟是如何运作的,但希望你会看到我的目标。假设我想在具有以下属性的图表上放置一个ClassNode

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

这将被包装为命令对象。说这是一个DiagramController。然后图控制器的责任可以是记录该命令(推入堆栈将是我的赌注)并将命令传递给DiagramBuilder。 DiagramBuilder实际上负责更新显示。

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);
  }
}

有些事情应该这样做,当然会有很多细节需要解决。顺便说一句,你的节点拥有的属性越多,Undraw就越有必要。

使用id将堆栈中的命令链接到绘制的元素可能是个好主意。这可能是这样的:

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);
  }
} 

此时,由于您拥有ID,因此您不必必须拥有对象,但如果您决定实施重做功能,则会有所帮助。为节点生成id的一种好方法是为它们实现一个hashcode方法,除非你不能保证不会以一种会导致哈希码相同的方式复制你的节点。 / p>

问题的下一部分是在你的DiagramBuilder中,因为你正在试图弄清楚如何处理这些命令。尽管如此,我只能确保您可以为可以添加的每种类型的组件创建反向操作。要处理脱钩,您可以查看edge-connection属性(我认为代码中的链接),并通知每个边连接它们要从特定节点断开连接。我认为断线时他们可以适当地重绘。

总而言之,我建议不要在堆栈中保留对节点的引用,而应该只是一种表示该点上给定节点状态的令牌。这将允许您在多个位置表示撤消堆栈中的同一节点,而不会引用同一个对象。

如果你有Q,请发帖。这是一个复杂的问题。

其他提示

以我的拙见,你会以一种比实际更复杂的方式来思考它。为了恢复到先前的状态,根本不需要克隆整个节点。而是每个 * * Command类都有 -

  1. 引用它所采用的节点,
  2. memento对象(状态变量足以让节点恢复)
  3. execute()方法
  4. undo()方法。
  5. 由于命令类引用了节点,因此我们不需要ID机制来引用图中的对象。

    在您问题的示例中,我们希望将节点移动到新位置。为此,我们有一个NodePositionChangeCommand类。

    public class NodePositionChangeCommand {
        // This command will act upon this node
        private Node node;
    
        // Old state is stored here
        private NodePositionMemento previousNodePosition;
    
        NodePositionChangeCommand(Node node) {
            this.node = node;
        }
    
        public void execute(NodePositionMemento newPosition) {
            // Save current state in memento object previousNodePosition
    
            // Act upon this.node
        }
    
        public void undo() {
            // Update this.node object with values from this.previousNodePosition
        }
    }
    
      
        

    链接怎么样?它们也应该是可移动的,但我不想仅仅为链接创建命令(而且只为节点创建一个命令)。

      

    我在GoF书中(在纪念模式讨论中)读到了节点位置变化的链接移动由某种约束求解器处理。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top