java memento_java – 使用Memento模式(和Command)存储复杂对象的状态

该博客讨论了在Java中开发UML编辑器时如何实现撤销/重做功能。作者提到已经实现了图形构建框架,但意识到忘记添加Do/Undo功能。考虑使用Memento和Command模式来解决这个问题,作者提出了一个解决方案,包括抽象命令类和针对不同操作(如移动、添加/删除、修改)的具体命令。博客中还提到了如何处理节点和链接的移动,以及如何在不直接引用图中对象的情况下实现撤销/重做操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我正在研究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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值