其实要说撤销重做的设计思路,其实没有啥特别的.这里主要还是针对双向同步的情况. 同步时有的人在增,有的人在删,况且操作因为网络的问题还有滞后性,又不能先协调后生效(这样同步体验太差), 一想就是一团乱麻.
单机撤销
首先来说单机情况的撤销重做. 通常的思路就是做一个操作链:
[START]~_p_[END] 一个当前指针pNow指向[END]
[START]~[+A]~[+B]~[+C]~[-C]~[-B]~[-A]~_p_[END] 这是做了三次增和三次删操作的操作链pNow也是指向[END]
此时执行撤销1次,pNow前移,pNow经过的节点被返回,告诉调用者是”删除”动作,应该执行”恢复”. 执行后的操作链变成这样:
[START]~[+A]~[+B]~[+C]~[-C]~[-B]~ _p_ [-A]~ [END].
那么什么时候会删除或者说截断操作链呢? 这要看策略,至少有两种策略.
- 策略1: 当产生新的操作时,pNow后面的操作记录全部删除. 这是绝大多数撤销重做功能的策略.后面记录中的有些对象如果没有必要可以真正的删除.
策略2: 总是在pNow指针前面插入,pNow之后的操作不删除. 这种策略也有少数软件采用.这种策略的效果是所有动作都不会消失.
+A,+B,撤销,+C这套动作,在策略1中会留下+A和+C两个操作,而策略B会留下+A+C+B三个操作.
真正删除
一般来说,撤销都是存在步数限制的,这也可以理解为操作链长度限制. 一次撤销清掉的对象肯定是不能删除的,它要隐藏起来,重做的时候可以恢复. 对于策略2一般就不删任何操作了(否则它的优势就没了),只就策略1来讲哪些对象是可以真正删除,哪些又不可以呢?
对于单纯单机的思路,可以做一些合理假设,如增操作后面只能是删操作,不可能增2次或删两次. 一个操作链中只可能有某对象的一对增删操作,因为正常操作不可能把删除的对象又add进来(当然这也不是绝对,如果可以add回来就不能这么假设了),且增操作总是先于删.
有以上假设后,哪些对象可以真删的判断变的比较容易,但是我要说双向同步时的情况更复杂,你不能保证你删的时候,是不是另一个成员也删了你删的这个对象,任何假设都只会增加不稳定因素.需要一种更笨更稳妥可靠的办法.
笨办法就是把pNow前面的全部涉及对象都收集起来,然后处理pNow后面要删的操作,凡是前面存在操作的对象,都不能真删对象.
如果采用策略2也就不用考虑要删谁了,全部保留!
唯心主义同步思路
一种思路是撤销重做作为一个指令,交由裁决者或者叫他主持,主持决定指令影响的对象ID,将指令+ID通知到其他人.这样的思路其实已经是经过深思熟虑的了.
“唯心主义”的设计思路.且不说哪种好, 我也没有能完全理清. 做法是任何一端,不管你是添加,删除操作,还是撤销,重做操作,结果就是使得对象增加或者移除了. 我只要把结果通知出去,至于我是何种方式造成的,你不用管.
那么在”+A,+B,撤销,撤销”的操作序列下,对方得到的同步通告就是+A,+B,-B,-A. 也就是说对方在不操作的情况下,看到A和B出现又消失了. 如果此时对方不断使用”撤销”,他将把他刚才看到的4个(注意是4个不是2个)动作撤销了一遍,而刚才的操作方如果还”撤销”的话,因为pNow已经移到操作链首,不会有任何反应.
更进一步,对方的四次撤销,最后结果是AB都不见了. 但是对开始的操作方来说,它的撤销给我增加了4个操作,分别是+A+B-B-A
最后的结果是虽然双方的操作链并不相同,但是动作却神奇的同步了(限于不同时删除同一个对象的情况).
这套办法的优点就是在纷乱复杂的情况下兼顾自己的操作链和操作最终同步.
缺陷(TODO)
同时删除同一对象缺陷
很显然,上面的唯心主义同步思路的描述还是没有解决同时删除同一对象的问题.如果任其发展的话,操作双方会得到+A-A-A这样的操作链,如果撤销两次难保不会引发给多的问题. 所以这个缺陷需要控制不再重复加入-A操作
真正删除缺陷
前面提到,操作链根据策略1是会真正删除一些对象的.有这么一种情况,甲执行+A,撤销,+B,乙的操作链是+A-A+B. 甲的操作链由于真正删除,变成+B 只有一个+B,且A已经被真正删除.那么如果乙撤销两次,A在乙上重新出现,而甲却没有了对象A. 这个缺陷的处理考虑是否采用策略2,但总是有点亏.