使用Command模式实现撤销机制 (Code Project 精选翻译)

翻译 2004年10月29日 09:25:00

使用Command模式实现撤销机制[1]

 

Writen by Matt Berther

Translated by Allen Lee[2]

Reviewed by Teddy Tam & Allen Lee

 

Introduction

Command是一个非常强大的设计模式,它的作用是将一个请求封装成一个对象,从而使你能够把来自客户端的不同请求(request)、队列(queue)或者日志记录请求(log request)包装成参数,并且还支持可撤销操作。

这个模式的一个最大的优点就是,它能够把执行某操作的对象和实际知道如何处理该操作的对象之间的耦合度降低。

今天,我要向大家介绍如何使用这个Command模式来实现撤销功能。至于我们的例子,我们将会开发一个非常简单的类记事本(Notepad clone)。你无须大惊小怪,因为这(个记事本)已经能够展现出这个模式的威力了。

 

The Code

我们要做的第一件事就是创建一个用于包装TextBox控件的抽象层(abstraction)。在Command模式里,这个抽象层被称为接收者(Reciever)。在我们的例子里面,这个接收者是一个叫做Document的对象。

 

None.gifclass Document
ExpandedBlockStart.gif
{
InBlock.gif    
private TextBox textbox;
InBlock.gif
InBlock.gif    
public Document(TextBox textbox)
ExpandedSubBlockStart.gif    
{
InBlock.gif        
this.textbox = textbox;
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public void BoldSelection()
ExpandedSubBlockStart.gif    
{
InBlock.gif        Text 
= String.Format("<B>{0}</B>", Text);
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public void UnderlineSelection()
ExpandedSubBlockStart.gif    
{
InBlock.gif        Text 
= String.Format("<u>{0}</u>", Text);
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public void ItalicizeSelection()
ExpandedSubBlockStart.gif    
{
InBlock.gif        Text 
= String.Format("<I>{0}</I>", Text);
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public void Cut()
ExpandedSubBlockStart.gif    
{
InBlock.gif        textbox.Cut();
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public void Copy()
ExpandedSubBlockStart.gif    
{
InBlock.gif        textbox.Copy();
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public void Paste()
ExpandedSubBlockStart.gif    
{
InBlock.gif        textbox.Paste();
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public string Text
ExpandedSubBlockStart.gif    
{
ExpandedSubBlockStart.gif        
get return textbox.Text; }
ExpandedSubBlockStart.gif        
set { textbox.Text = value; }
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

我们要为Document对象定义的是那些能脱离当前文档而执行的操作,并把该功能从我们的主程序中完全解耦。将当前选定的文本变为黑体后,若要改变这个设置,我们应回到这个对象而非界面代码。

接着,我们需要设计一下Command的接口[3]。由于某些命令不要求撤销功能(例如“复制”),于是我们需要创建两个基类(Command和UndoableCommand)。稍后我们将会看到如何整合UndoableCommand。而现在,你只需记住这是一个用于被其它需要具备撤销功能的命令继承的类就行了。

 

None.gifpublic abstract class Command
ExpandedBlockStart.gif
{
InBlock.gif    
public abstract void Execute();
ExpandedBlockEnd.gif}

None.gif
None.gif
public abstract class UndoableCommand : Command
ExpandedBlockStart.gif
{
InBlock.gif    
public abstract void Undo();
ExpandedBlockEnd.gif}

 

随着我们完成我们的应用程序并向其中加入菜单项,我们将会看到我们需要一个Command对象来处理每这些菜单操作。那么,要处理黑体化,让我们先写下如下代码:

 

None.gifclass BoldCommand : UndoableCommand
ExpandedBlockStart.gif
{
InBlock.gif    
private Document document;
InBlock.gif
InBlock.gif    
private string previousText;
InBlock.gif
InBlock.gif    
public BoldCommand(Document doc)
ExpandedSubBlockStart.gif    
{
InBlock.gif        
this.document = doc;
InBlock.gif        previousText 
= this.document.Text;
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public override void Execute()
ExpandedSubBlockStart.gif    
{
InBlock.gif        document.BoldSelection();
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public override void Undo()
ExpandedSubBlockStart.gif    
{
InBlock.gif        document.Text 
= previousText;
ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

 

通过创建一个文档对象来包装TextBox,我们可以降低将要执行某操作的对象(菜单项)和实际知道如何处理该操作的对象(文档对象)之间的耦合度。

剩余命令对象与上述非常相似,因此我就不一一介绍所有的代码了,而且这些代码可以通过下载获得。

接下来我们需要一个将所有的这些命令整合到一起的CommandManager。CommandManager是一个非常简单的类,它的内部只有一个堆栈(stack),该堆栈用于保存并跟踪我们那些具备撤销功能的命令。

 

None.gifclass CommandManager
ExpandedBlockStart.gif
{
InBlock.gif    
private Stack commandStack = new Stack();
InBlock.gif
InBlock.gif    
public void ExecuteCommand(Command cmd)
ExpandedSubBlockStart.gif    
{
InBlock.gif        cmd.Execute();
InBlock.gif        
if (cmd is UndoableCommand)
ExpandedSubBlockStart.gif        
{
InBlock.gif            commandStack.Push(cmd);
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
public void Undo()
ExpandedSubBlockStart.gif    
{
InBlock.gif        
if (commandStack.Count > 0)
ExpandedSubBlockStart.gif        
{
InBlock.gif            UndoableCommand cmd 
= (UndoableCommand)commandStack.Pop();
InBlock.gif            cmd.Undo();
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

ExpandedBlockEnd.gif}

 

从上面的代码我们可以看到,我们仅仅把那些是UndoableCommand的命令加入到撤销堆栈。还记得我曾经说过我们将看到如何整合它(UndoableCommand)吗?这就是了。我们不希望不具备撤销功能的命令备加入到该队战中。若用户尝试撤消本身不支持撤消功能的某物,将得不到回应。

余下的工作就是替换菜单项的事件处理程序(有需要也可替换工具拦的)。

 

None.gifpublic class MainForm : System.Windows.Forms.Form
ExpandedBlockStart.gif
{
InBlock.gif    
private System.Windows.Forms.TextBox documentTextbox;
InBlock.gif
InBlock.gif    
private CommandManager commandManager = new CommandManager();
InBlock.gif
InBlock.gif    
private Document document;
InBlock.gif
InBlock.gif    
public MainForm()
ExpandedSubBlockStart.gif    
{
InBlock.gif        
//
InBlock.gif        
// Required for Windows Form Designer support
InBlock.gif        
//
InBlock.gif
        InitializeComponent();
InBlock.gif        
InBlock.gif        document 
= new Document(this.documentTextbox);
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
// a bunch of snipped code
InBlock.gif

InBlock.gif    
private void cutMenuItem_Click(object sender, System.EventArgs e)
ExpandedSubBlockStart.gif    
{
InBlock.gif        commandManager.ExecuteCommand(
new CutCommand(document));
ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    
private void pasteMenuItem_Click(object sender, System.EventArgs e)
ExpandedSubBlockStart.gif    
{
InBlock.gif        commandManager.ExecuteCommand(
new PasteCommand(document));
ExpandedSubBlockEnd.gif    }

InBlock.gif    
InBlock.gif    
// etcdot.gif
ExpandedBlockEnd.gif
}

我写了一个完整的示范程序并提供了下载。请注意,在这里,这个文本编辑器是尚未完善的初级品。而你的任务,如果你愿意接受的话,为这个程序加入重做(redo)的功能[4]

希望我已阐明此模式之威力及如何轻松使用其来添加合成功能。现在添加新的命令非常容易了,因为你不再需要触及任何现有的代码。

 

Comments by Allen Lee

  • 版权问题:文章版权归原作者所有,此译文版仅供学习和研究之用。有关作者的资料以及完整的源代码请跳到Using the Command pattern for undo functionality(原文)。
  • 翻译工作:本文首先由我完成翻译初稿,并在需要讨论的翻译点进行注释;然后提交给Teddy进行第一次审校;接着由我和Teddy共同就相关需要斟酌和修正的翻译点进行讨论,并有Teddy浏览全文一次完成第二次审校;最后由我进行后期工作(包括排版)时再通读全文完成最终稿。在此期间,非常感谢Teddy对本文审校工作的大力支持。他不但对本文进行基本审校,而且还用他老练的英语翻译功底为本文多处地方润色。
  • 此处原句为:Secondly, we will need to design our Command interfaces. 这里的interface是有别于C#的关键字(keyword)interface的。
    • 前者指的是对象对外公开发布(publish)的一个或多个成员;而后者却相当于一张完整的契约,契约里面会明确规定遵守契约的对象必须实现的一个或多个成员。
    • 用一个更加接近现实的例子——三脚插座,每一个插孔都是一个独立的对外(公开发布)的接口,此为前者之概念;而每一个三角插座,必须遵守以下约定:提供三个插孔,分别用于接火线、接零线和接地,此为后者之概念。
    • 换一句话,前者之概念可以是后者之概念的一个或多个成员。不过这里还是统一翻译成接口,希望不会造成不必要的误解。
    • “救命啊!好好的一篇文章,你为什么...?“我无意在这里分解概念来增加大家的思想负担,如果你看到这里,打从心底冒出一句类似的话,那么,我建议你无视本注释的存在,只要你能够从文章中学到应该学到的东西(该是什么呢?)!
  • 最后,原文作者向你们布置了家庭作业,就是动手实现一个重做的功能,完善那个未完善的初级品。再好的理论、再有吸引力的文章,如果没有实践,那只能是一场空谈,也是在浪费时间!So go practising!

 

使用Command模式实现撤销机制[1]

 使用Command模式实现撤销机制[1] Written by Matt BertherTranslated by Allen Lee[2]Reviewed by Teddy Tam & Allen...
  • Feisy
  • Feisy
  • 2008年11月06日 16:17
  • 710

命令模式—C++实现撤消重做

Command结构 意图 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。 适用性 1、抽象出待执行的动作以参数化某对象,你可用...
  • wcl0617
  • wcl0617
  • 2017年11月22日 14:25
  • 173

设计模式 - 命令模式(command pattern) 撤销(undo) 详解

命令模式(command pattern) 撤销(undo) 详解本文地址: http://blog.csdn.net/caroline_wendy参考命令模式: http://blog.csdn.n...
  • u012515223
  • u012515223
  • 2014年06月16日 19:43
  • 3056

撤销功能的实现——备忘录模式

什么是备忘录模式备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,当前很多软件都提供了撤销(Und...
  • Todo_
  • Todo_
  • 2016年03月04日 17:37
  • 1212

撤销功能的实现----备忘录模式

每个人都有过后悔的时候,但人生并无后悔药,有些错误一旦发生就无法再挽回,有些人一旦错过就不会再回来,有些话一旦说出口就不可能再收回,这就是人生。为了不后悔,凡事我们都需要三思而后行。说了这么多,大家可...
  • xue_jiang_han2013
  • xue_jiang_han2013
  • 2014年05月21日 15:22
  • 545

设计模式(14)-命令模式

命令模式(Command Pattern):将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为...
  • panweiwei1994
  • panweiwei1994
  • 2017年07月17日 11:38
  • 837

Command模式实现撤销重做(Undo/Redo)

这是在实际项目中遇到的需求,项目中使用了Java Swing画界面,开始时没有实现撤销重做,后期要求加入撤销重做功能。在网上查找到资料说这种撤销重做的操作一般通过Command模式来实现,在实现过程中...
  • w1014074794
  • w1014074794
  • 2016年03月24日 22:30
  • 1607

撤销功能的实现——备忘录模式(Memento Pattern)

一、引言 备忘者模式与命令模式有点相似,不同的是,命令模式保存的是发起人的具体命令(命令对应的是行为),而备忘录模式保存的是发起人的状态(而状态对应的数据结构,如属性)。下面具体来看看备忘录模式。 ...
  • bigpudding24
  • bigpudding24
  • 2015年07月14日 18:06
  • 613

使用Command模式实现应用的撤销功能

使用Command模式实现撤销机制[1]   Written by Matt Berther Translated by Allen Lee[2] Reviewed by Teddy Tam ...
  • xiaocanbanyue
  • xiaocanbanyue
  • 2012年12月18日 11:26
  • 205

IOS------Warning

处理警告: 1,Validate Project Settings(update to recommended settings) A: 2,'xxxxxxx' is deprecated:fi...
  • shijiucdy
  • shijiucdy
  • 2013年04月03日 14:33
  • 6718
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用Command模式实现撤销机制 (Code Project 精选翻译)
举报原因:
原因补充:

(最多只允许输入30个字)