zoukankan      html  css  js  c++  java
  • undo机制分析

    相信有不少RIA应用都有undo/redo功能。这里我就拿自己做过的画图板为例子说明一下它的实现原理(没有啥有用的代码,理解原理就行)。

    分析

    undo是什么?在用word的时候,写了一行字后悔了,执行一下undo那行字就消失了。undo就这么简单,将做过的事情再倒退回去。说专业一点,就是执行一个逆向动作。拿画图板里最简单的画直线来讲,画了一条直线,对应的undo就是擦掉这个线。很多的命令也都是类似的。所以我们看到,一个命令可以被“undo”,它至少包含了两种动作,一个是do,一个是undo,他们是相互逆向的动作。下面的事情就明朗了,如果一个命令被认为是可以undo的,那么它至少要实现两个方法,do和undo(大多数时候redo就是do)。但是命令千变万化,比如画直线和画曲线就不同,这里就需要用到设计模式中的Command。
    代码:

    代码
    package com.drawing
    {
            
    public interface ICommand
            {
                    
    public function doIt():void //do
                    public function undoIt():void //undo
            }
    }


    我定义了一个ICommand接口,它按照我分析的定义了两个方法do和undo,所以认为它是可以被undo的。Command代表的不是一个实实在在的组件,而是一个动作。所有的具体动作都是来自ICommand。比如画直线,它就可以这么定义。
    代码:

    代码


    如果需要额外参数,从构造函数或者public方法注入都可以。当实际使用的时候,只要直接调用doIt或者undoIt就行了,根本不必管它是啥方法。
    undoManager一统天下

    分析完了undo原理,下面要做的就是把很多可以undo的命令组合起来思考。o(∩_∩)o…如果很多undo,redo混在一起就会发现事情也不是那么简单。这里有必要搞个undoManager来管理一下。还是先从原理开始分析,观察一下word,如果你分别写了三行字,undo两回后再写了一行字,然后你又想把原来的第二行恢复,发现无论如何也不能恢复!是不是有点乱?这里你只要明白一点就可以了:如果有一个新动作执行,那么原来已经被undo的动作就不能再redo了。用下面的图可以说明。

    如果把command看成一条链的话,先执行ABCD,undo一次就退回倒C,再undo就退回到B,如果有新的command加进来而不是继续undo或redo,新的command链ABE便会形成,CD就会被抛弃,再也不能redo了。从AS3角度,准备两个堆栈结构(Array),分别代表现存的已经执行过的command(如AB),和已经被undo过的command(以后可以被redo也可以被抛弃,如CD)。看代码。
    代码:

    代码
    package com.drawing
    {
            
    public class UndoManager implements ICommand
            {
                    
    public function UndoManager()
                    {
                    }

                    
    // 一条队列存放已经执行过的command
                    private var queue:Array = new Array();
                    
    // 一条队列存放已经undo过的command
                    private var undoQueue:Array = new Array();

                    
    public function addNewCmd(cmd:ICommand):void
                    {
                            undoQueue 
    = new Array();//新command加入的时候,undo队列便被清空
                            queue.push(cmd);
                    }

                    
    /////////////////////////////////
                    
    //redo
                    public function doIt():void
                    {
                            
    //TODO: implement function
                            if(undoQueue.length>0)
                            {
                                    var ic:ICommand 
    = undoQueue.pop() as ICommand;
                                    ic.doIt();
    //redo

                                    queue.push(ic);
                            }
                    }

                    
    //undo
                    public function undoIt():void
                    {
                            
    //TODO: implement function
                            if(queue.length>0)
                            {
                                    
    //把队列尾部的command拿出来执行undo
                                    var ic:ICommand = queue.pop() as ICommand;
                                    ic.undoIt();

                                    
    //执行完后插入undoQueue
                                    undoQueue.push(ic);
                            }
                    }

            }
    }


    实际用的时候很简单了,执行完一个command就addNewCmd到UndoManager,如果想undo或redo就直接操作UndoManager,根本不必管现在是啥状态,是不是很简单。

  • 相关阅读:
    GitHub 访问不顺怎么办?在线等,急
    深度解读最新版 Scrum 指南
    有奖体验 CODING 产品,iPad Pro、HHKB 键盘等超级礼包等你来!
    腾讯全资子公司 CODING 2021 届校园招聘正式开启!
    产品更新 | 「CODING 持续部署」新手体验:应用发布只需 30 秒!
    CODING 推出独立制品仓库 WePack,助力企业渐进式 DevOps 转型
    CODING DevOps 线下沙龙回顾一:DevOps 代码质量实战
    CODING 荣获「2020 DevOps 领域年度极具影响力产品」奖项
    CODING DevOps 产品认证学习计划正式启动!
    DevOps Workshop | 代码管理入门:基于代码扫描实现团队效率提升
  • 原文地址:https://www.cnblogs.com/sevenyuan/p/1614652.html
Copyright © 2011-2022 走看看