Monday, April 26, 2010

【CSDN】有关设计模式的一点讨论

举个例子,比如我新建了一个模板。模板里面可以添加无数个图层,每个图层都有N多个操作动作。
所有图层的操作都是串行进行的,现在要实现PS里面的撤销重复功能,如下图
步骤 0 1 2 3 4 5
类型 新增layer1 新增layer2 move layer1 删除layer2 新增layer3 move layer3
那撤销时,从第5个步骤往回一步步执行
我的实现方法是,先定义一个动作命令基类,再定义一些动作命令的派生类

   1:  type
   2:    TCommand = class
   3:    private
   4:      FLayerData : TData;//对应的图层数据结构体
   5:    protected
   6:      procedure DrawLayer; virtual; abstract;  
   7:    public
   8:      procedure Execute;//在这个过程中执行 DrawLayer过程
   9:    end;
  10:    //移动
  11:    TCommandMove = class
  12:    private
  13:      FLayerData : TData;
  14:    protected
  15:      procedure DrawLayer; override;  
  16:    public
  17:    end;
  18:    //重画
  19:    TCommandReDraw = class
  20:    private
  21:      FLayerData : TData;
  22:    protected
  23:      procedure DrawLayer; override;  
  24:    public
  25:    end;





新建的模板也有一个对应的类


  TComBine = class
private
FList : TList ; //用于添加动作类
FCommand : TCommand ; //动作命令类
FCurIndex : integer; //当前执行的操作列表下标
protected
public
property Items[Index : integer] : TCommand read SetItems write GetItem;
end;





每进行一个操作,就添加在TComBine 中添加一个具体的动作命令类对象,然后让动作命令类对象去执行相应的操作
撤销和重复时,只要修改FCurIndex 属性,然后用 TCommand(items[i]).Execute;
当有撤销操作时,再新增操作,直接将当前步骤之后的动作命令类对象全部销毁,类似于PS中的功能(撤销后再操作,当前步骤之后的操作就没了)
现在出来的问题:
比如从第5撤销到第4步时,它的操作应该是将 layer3 移到新建的位置,那我具体要怎么处理?直接执行第四步的话,它会创建一个LAYER,这显然不符合要求
又或者从第4撤销到第3步时,实际操作是删除layer3,直接执行的结果是将layer2删除
可能是我的处理模式本来就有问题,不知道大家有没有什么可行的方案可分享一下?不胜感激!!



网友 CoderPlusPlus 提出了一种方法:





你的每个动作类都定义Execute方法和Undo、Redo方法,例如新建动作,Execute方法里进行新建,Undo里执行删除,Redo里也是新建
用一个栈来保存命令,执行命令时先将其入栈,然后执行Execute,需要撤销时栈顶做临时回退(此时只是栈顶的移动,实际命令对象还没有真正出栈,同时记录原栈顶),同时对经过的每一个命令对象执行Undo,在执行若干次撤销后有两种情况,如果调用了新的命令,此时此前撤销过的命令对象真正出栈,而如果选择重复,则栈顶前进,经过的命令对象执行Redo方法,这样可实现任意次的撤销与重复
总之思想就是把命令会引起变化的状态的初值和终值由命令对象来保存
下面是一个简单示意图:



{依次执行C1、C2、C3命令后的命令栈}



C1——C2——C3(Top)



{执行两次撤销后的栈,此时C2、C3处于“待定状态”}



C1(Top)——C2——C3



{执行一次重复,此时C3处于“待定状态”}



C1——C2(Top)——C3



{执行C4命令}


C1——C2——C4(Top)



weiym给出了如下的代码和分析:



LZ有没有看我那个程序, 实际上用的是设计模式里的Command模式,
那里面包含3种操作:新画一个图形, 橡皮擦掉一个图形,改变选择区域,
分别对应不同的Command: CSCGraphAddCmd,CSCGraphEraseCmd,CSCSelectRectChangeCmd.
然后由CSCCanvasCmdMgr来对所有的操作进行管理,它里面有2个Stack: m_arUndoCmd和m_arRedoCmd, 分别保存Undo和Redo的操作。

class CSCCanvasCmdMgr: public CRefCountBase
{
public:
BOOL IsUndoStackEmpty();
BOOL IsRedoStackEmtpy();

BOOL Undo();
BOOL Redo();

VOID OnAddGraphDone(CRefPtr<CSCGraphObject> pGraph);
VOID OnEraseGraphDone(CRefPtr<CSCGraphObject> pGraph);

VOID OnSelectRectChangeDone(CRect& rtOld, CRect& rtNew);

protected:
std::list<CRefPtr<CSCCmdBase> > m_arUndoCmd;
std::list<CRefPtr<CSCCmdBase> > m_arRedoCmd;
};



当你新画了图形时, 传入新画的图形对象,调用OnAddGraphDone, 在里面构造一个新画Command,保存到Undo的Stack;如果要撤销你的新画动作, 就从Undo Stack里面把你的最新Command出栈,并调用它的Unexecute(), 因为你的这撤销动作也能Redo, 所以同时又要把它存到redo的Stack;如果你要redo这个动作,就把它存会undo stack, 同时调用它的Execute().
然后就要看你各个动作的Execute和Unexecute怎么实现了。
总之,原理就是把你的各个动作封装成就Execute和Unexecute功能的Command对象,然后参照上面的原理就可以了。





我感觉这个就是典型的Command模式,遇到典型的例子真是又形象又直接!收益非浅!

 

No comments:

Post a Comment