Undo/Redo是CAx软件中常见的操作功能,其实现方法也相对比较成熟,本文对FreeCAD Transaction机制进行深入分析,一方面是为了深化对FreeCAD代码的理解,学习其设计思路,领略其设计模式的使用范式;另一方面则考虑到Undo/Redo功能的普遍性,旨在阐述Undo/Redo的实现原理,希望对从事国产CAx软件开发的朋友有所帮助。
注1:限于笔者研究水平,难免有理解不当,欢迎批评指正。
注2:文章内容会不定期更新,欢迎交流讨论。
一、预修知识
1.1 设计模式
Undo/Redo经典实现是采用Command、Memento等设计模式。GoF、Alexander Shvets等已经就Command、Memento等相关设计模式进行了经典阐述,这里不再赘述,仅简要罗列其技术要点。
Command模式将请求封装成了对象,提供了命令响应的统一接口。
Memento模式在不违反封装的前提下,提供了对象状态记录与恢复的功能。
FreeCAD基于Observer模式,实现了App::Property类。按照GoF's Observer模式,Property作为Subject,而App::PropertyContainer则是Observer。
二、代码分析
2.1 App::TransactionalObject
FreeCAD中,整套代码最为基础的类其实只有两个,一个是App::DocumentObject,另一个则是Gui::ViewProvider,而这两个类均派生于App::TransactionalObject。
App::TransactionalObject类比较简单,主要功能是采用Memento模式实现了对象属性快照的功能。
每当修改属性数据时,便会自动调用onBeforeChangeProperty()函数,而该函数的主要作用就是存储对象相关属性,以便后续执行Undo时,可以完成对象状态的恢复。
(完整代码见原文)
2.2 App::TransactionObject
由于一个Transaction通常由多个子操作构成,对应于子操作,App::TransactionObject则是用于记录对象创建、对象删除、属性修改等操作历史。
(完整代码见原文)
需要指出的是,App::TransactionObject通过枚举值New、Del来标记对象创建、对象删除,并记录对象名称;而将属性存储在App::TransactionObject::_PropChangeMap中。
2.3 App::Transaction
App::Transaction正是用于记录一次Transaction中对同一文档的修改,其中可能设计对多个对象的修改。
(完整代码见原文)
实际使用中,每个Document中都分别定义了自己的App::Transaction对象,因此记录的也就是对本文档对象的修改。
(完整代码见原文)
2.4 App::Document
按照GoF Memento模式,App::Document实际上扮演的是Caretaker的角色,App::Document提供了Undo/Redo堆栈,
2.5 Gui::Command
Gui::Command对应的就是GoF Command模式中的Command,提供了命令响应的接口,同时提供了Transaction创建的调用接口。
参考资料
Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides. Design Patterns:elements of reusable object-oriented software. Addison Wesley, 1994.
Alexander Shvets. Dive into Design Patterns.