我正在研究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合并.这个类现在是空的.我可能会使用一种机制来确定修改后的对象是节点还是边缘(通过实例或ID中的特殊标记).
这是一个很好的解决方案吗?
解决方法:
我认为你只需要将你的问题分解成更小的问题.
第一个问题:
问:如何用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 (graphicalRefId, node);
this._commandStack.push(nodePair);
}
public void Undo()
{
var nodePair = this._commandStack.pop();
this._diagramBuilderUndraw(nodePair.Key);
}
}
此时,您不必拥有该对象,因为您拥有该ID,但如果您决定同时实现重做功能,则会有所帮助.为节点生成id的一种好方法是为它们实现一个哈希码方法,除非你不能保证不会以一种导致哈希码相同的方式复制你的节点.
问题的下一部分是在你的DiagramBuilder中,因为你试图弄清楚如何处理这些命令.尽管如此,我只能确保您可以为可以添加的每种类型的组件创建反向操作.要处理脱钩,您可以查看edge-connection属性(我认为代码中的链接),并通知每个边连接它们要从特定节点断开连接.我认为断线时他们可以适当地重绘自己.
总而言之,我建议不要在堆栈中保留对节点的引用,而应该只是一种表示该点上给定节点状态的令牌.这将允许您在多个位置表示撤消堆栈中的同一节点,而不会引用同一对象.
发帖如果你有Q的话.这是一个复杂的问题.
标签:java,command,design-patterns,state,memento
来源: https://codeday.me/bug/20190622/1260956.html