实现可撤消命令-Java

实现可撤消命令-Java,java,model-view-controller,design-patterns,memento,Java,Model View Controller,Design Patterns,Memento,我目前正在编写一个小的文本编辑器(学校项目),在找到一个好的、干净的方法来管理可撤销的命令时遇到了一些问题 (这不是一个代码审查问题,因为这不仅仅是关于改进。如果我想让我的应用程序按我所希望的那样工作,我需要更改我的代码) 下面是我的应用程序的工作原理:我有一个具体的主题,它包含缓冲区,实际上是一个字符数组列表。每当修改此arraylist(插入、剪切、粘贴…)时,主题都会更新观察者(目前仅包含在一个gui中)(MVC模式) 到目前为止,我对撤消和重做所做的是保存缓冲区的整个状态(通过memen

我目前正在编写一个小的文本编辑器(学校项目),在找到一个好的、干净的方法来管理可撤销的命令时遇到了一些问题

(这不是一个代码审查问题,因为这不仅仅是关于改进。如果我想让我的应用程序按我所希望的那样工作,我需要更改我的代码)

下面是我的应用程序的工作原理:我有一个具体的主题,它包含缓冲区,实际上是一个字符数组列表。每当修改此arraylist(插入、剪切、粘贴…)时,主题都会更新观察者(目前仅包含在一个gui中)(MVC模式)

到目前为止,我对撤消和重做所做的是保存缓冲区的整个状态(通过memento),这很好,但是:

  • 显然可以改进
  • 不适用于我需要实现的新功能(记录用户操作以便他可以随时播放)
我不知道如何保存命令而不是缓冲区,也不知道这是否是一个好方法

下面是一些程序块(用于insert命令):

GUI.java

...

class KeyboardListener extends KeyAdapter { 

    @Override
    public void keyPressed(KeyEvent e) {
            ...
            commandManager.executeCommandInsert(caretStart, caretStop, e.getKeyChar());
            ...
    }
    ...
}
...

public void executeCommandInsert(int start, int end, char character) {  
    addMementoUndo();   
    commandInsert.setCarets(start, end);
    commandInsert.setChar(character);
    commandInsert.execute();    
}

public void addMementoUndo() 
{
    TextConcrete text = TextConcrete.getInstance();
    this.commandsUndo.add(0, text.createMemento());
    if(recordMode){
        this.recordings.add(text.createMemento());
    }
}
...
...
public void execute(){
   TextConcrete text = TextConcrete.getInstance();
   text.insert(this.start, this.end, this.character);
}
...
...
public void insert(int start, int end, char character){
    //inserting in ArrayList
}

public CommandUndoable createMemento(){
   CommandUndoable mem = new CommandUndoable();
   mem.setState(getState());
   return mem;
}

public String getState(){
   StringBuilder temp = new StringBuilder();
   for(int idx = 0; idx < this.state.size(); idx++){
       temp.append(this.state.get(idx));
   }    
   return temp.toString();
}
...
CommandManager.java

...

class KeyboardListener extends KeyAdapter { 

    @Override
    public void keyPressed(KeyEvent e) {
            ...
            commandManager.executeCommandInsert(caretStart, caretStop, e.getKeyChar());
            ...
    }
    ...
}
...

public void executeCommandInsert(int start, int end, char character) {  
    addMementoUndo();   
    commandInsert.setCarets(start, end);
    commandInsert.setChar(character);
    commandInsert.execute();    
}

public void addMementoUndo() 
{
    TextConcrete text = TextConcrete.getInstance();
    this.commandsUndo.add(0, text.createMemento());
    if(recordMode){
        this.recordings.add(text.createMemento());
    }
}
...
...
public void execute(){
   TextConcrete text = TextConcrete.getInstance();
   text.insert(this.start, this.end, this.character);
}
...
...
public void insert(int start, int end, char character){
    //inserting in ArrayList
}

public CommandUndoable createMemento(){
   CommandUndoable mem = new CommandUndoable();
   mem.setState(getState());
   return mem;
}

public String getState(){
   StringBuilder temp = new StringBuilder();
   for(int idx = 0; idx < this.state.size(); idx++){
       temp.append(this.state.get(idx));
   }    
   return temp.toString();
}
...
CommandInsert.java

...

class KeyboardListener extends KeyAdapter { 

    @Override
    public void keyPressed(KeyEvent e) {
            ...
            commandManager.executeCommandInsert(caretStart, caretStop, e.getKeyChar());
            ...
    }
    ...
}
...

public void executeCommandInsert(int start, int end, char character) {  
    addMementoUndo();   
    commandInsert.setCarets(start, end);
    commandInsert.setChar(character);
    commandInsert.execute();    
}

public void addMementoUndo() 
{
    TextConcrete text = TextConcrete.getInstance();
    this.commandsUndo.add(0, text.createMemento());
    if(recordMode){
        this.recordings.add(text.createMemento());
    }
}
...
...
public void execute(){
   TextConcrete text = TextConcrete.getInstance();
   text.insert(this.start, this.end, this.character);
}
...
...
public void insert(int start, int end, char character){
    //inserting in ArrayList
}

public CommandUndoable createMemento(){
   CommandUndoable mem = new CommandUndoable();
   mem.setState(getState());
   return mem;
}

public String getState(){
   StringBuilder temp = new StringBuilder();
   for(int idx = 0; idx < this.state.size(); idx++){
       temp.append(this.state.get(idx));
   }    
   return temp.toString();
}
...
textcontracte.java

...

class KeyboardListener extends KeyAdapter { 

    @Override
    public void keyPressed(KeyEvent e) {
            ...
            commandManager.executeCommandInsert(caretStart, caretStop, e.getKeyChar());
            ...
    }
    ...
}
...

public void executeCommandInsert(int start, int end, char character) {  
    addMementoUndo();   
    commandInsert.setCarets(start, end);
    commandInsert.setChar(character);
    commandInsert.execute();    
}

public void addMementoUndo() 
{
    TextConcrete text = TextConcrete.getInstance();
    this.commandsUndo.add(0, text.createMemento());
    if(recordMode){
        this.recordings.add(text.createMemento());
    }
}
...
...
public void execute(){
   TextConcrete text = TextConcrete.getInstance();
   text.insert(this.start, this.end, this.character);
}
...
...
public void insert(int start, int end, char character){
    //inserting in ArrayList
}

public CommandUndoable createMemento(){
   CommandUndoable mem = new CommandUndoable();
   mem.setState(getState());
   return mem;
}

public String getState(){
   StringBuilder temp = new StringBuilder();
   for(int idx = 0; idx < this.state.size(); idx++){
       temp.append(this.state.get(idx));
   }    
   return temp.toString();
}
...
。。。
公共void插入(整数开始、整数结束、字符){
//插入ArrayList
}
公共命令可撤消createMemento(){
CommandUndoable mem=新CommandUndoable();
mem.setState(getState());
返回mem;
}
公共字符串getState(){
StringBuilder temp=新的StringBuilder();
for(int idx=0;idx
CommandUndoable只是保存缓冲区状态的纪念品,然后保存在管理员(CommandManager)的纪念品列表中

非常感谢您的帮助。

您可以创建一个可由命令实现的可撤销接口,例如:

public interface Undoable {

   public void do(Editor text)

   public void undo(Editor text)

}

其中编辑器是编辑器的模型,您可以在其中插入文本、从中删除文本或使用其他命令进行操作。实现接口的命令只需要知道如何“执行”和“撤消”本身,因此您不需要在每个步骤中存储文本缓冲区的副本,只需存储命令列表。

我更喜欢定义两个接口,但在它们的方法中没有任何参数

public interface Command {

    public void execute();

}
。。。对于撤消操作:

 public interface UndoableCommand extends Command  {

    public void undo();

}
我认为,这更灵活,因为您的应用程序中可能存在一些无法撤消的交互。(如保存或加载文件)

要实现这样一个
UndoableCommand
,只需编写这样的代码即可

 public class EditorInsertCommand implements UndoableCommand  {

    private Editor editor;

    private String textToInsert;

    private String textBefore;

    public EditorInsertCommand(Editor editor, String textToInsert){
       this.editor = editor;
       this.textToInsert = textToInsert;
    }


    public void execute() {
        this.textBefore = editor.getText();
        editor.setText(textToInsert);
    };

    public void undo(){
       editor.setText(textBefore );
    };

}

execute
undo
的处理也可以通过
CommandManager
完成。

是的,但您不是仍然使用该方法保存整个缓冲区吗?(
textfore
)您可以自由地在逻辑上实现这一点。这甚至是最简单的方法。我只是想展示一下
命令
可撤销命令
界面的用法。没错,他就是。因此,我不会保存整个文本,而是保存插入发生的位置和插入的文本。因此,撤销应该从修改后的文本中剪切插入的文本,而不是替换整个文本。然后您只需将
保存在列表中?有一个单独的CommandHandler类来处理撤销/重做机制。我在Mark Grand的《Java模式》一书中读到一个类似于您的解决方案,我只是稍微修改了一下它正在工作。谢谢你的帮助。有很多不同的方法来实现撤销/重做/重放。最常见的一种是使用“命令”设计模式,您可能想了解它(您提到了“memento”,但不清楚您是否指的是设计模式,因为memento模式通常不是用于撤消/重做/重放的模式)。如果/当你在不可变对象上执行OO时,你可以用一种更简单的方式实现和撤销/重做,只需简单地保存整个状态,但这会“浪费”内存。是的,我使用的是命令模式(命令界面,然后所有命令都作为单独的对象)。memento是保存整个缓冲区的方法,但是现在我对如何创建一个新的实现有些迷茫。