首页 > 代码库 > 架构反思案例之桌面程序架构案例
架构反思案例之桌面程序架构案例
这是一个大型的桌面版程序,前后有上百个人在这个软件的各个组件上奋战,这里介绍的部分基本上都是我留下足迹的部分,或者是参与设计,或者是重用这些组件,也或者是改造过这些组件。
插件架构
这个结构很清楚,是很多项目必选的架构,插件具有高度的灵活性和扩展性,这是这个架构突出的优点。
采用了这个架构的程序,通常有两种做法:
一种做法都是把每个单独的工程(单独的dll)作为单独的插件加载,在加载的过程中完成初始化的工作,在所有插件加载完毕以后根据插件的状态再初始化程序的界面,这也是这个项目的做法。
另一种做法是动态加载,程序会监测特定的目录,当这个目录内有变化的时候,比如有可以使用的dll添加或删除的时候,动态的去加载或卸载该dll,然后修改相关菜单和界面,这种做法有点像硬件上所谓的“热插拔”的意思。
动画设计
这个部分的设计其实包含3个相关的内容:
1. 渲染的方式
这个设计的基础是Override对象,它记录的是数据的变化,当数据发生变化后,我们不直接修改原数据对象的成员,而是创建一个对象来保存这种变化。
比如我们有一个对象叫长方形,它有一个成员数据表示其填充色,创建的时候我们直接设置为红色,然后当我们修改其颜色为绿色的时候,我们内部的实现是创建一个长方形对象对应的ColorOverride,该对象记录下了长方形的id和绿色。
渲染的时候我们会先检查数据对象是否有相关的Override,如果有,则其优先级为最高,用于渲染,没有则以对象自身属性渲染。
2. 动画元素的生成
动画元素(Action对象)的生成就是基于Override的设计来实现的。
我们使用简单的观察者模式,创建了ActionFactory对象来监听Override的创建,更新和删除事件,根据Override的变化我们可以捕捉相关的动画数据保存到Action中。
这个思路工作很好,直到TransformAction的出现,因为当用户操作对象移动的时候,用户的输入与得到的Override不再是一一对应了。比如一个螺丝旋转360度与720度虽然从最终得到的矩阵来说是完全一样的,但是用户希望的结果却是不一样的,360度意味着转一圈,而720度意味着转两圈。而且转90度与转270度对于用户来说也是不一样的,虽然最终的位置是一样的。
这种情况下,我们光靠监听Override已经不够了,此时需要完全记录下用户的输入数据,根据这个用户真实的数据,结合Override的值,我们就可以正确的还原用户的意图。
3. 动画的播放
播放动画过程是根据时间点,对生成的动画元素Action插值得到该时间点的属性值(EntityAttribute对象),然后根据这个属性值去更新相对应的Override,这样不断循环达到动画的效果。
这3个部分结合起来就可以完成一个完整的动画制作和播放功能。
命令模式
这个系统的设计也是中规中矩的,这里主要包括以下几个要点:
1. 程序启动的时候会初始化所有插件中的CommandDefinition。从这个类的名字上来说,这个类就是用于动态创建命令的。
2. 命令以一个字符串作为唯一的标示符,执行命令的时候,只需要在CommandExecutor中指定这个标示符就可以了。使用字符串传递的方式来解耦效果还可以。
3. CommandExecutor拿到字符串以后会根据这个标示符找到相关的CommandDefinition对象,然后创建Command和CommandParameter对象,接下来就是执行Command。
4. 执行Command的过程中并不是直接修改Model层中的数据,而是调用Model层中提供的Request对象修改Model中的数据,这样很容易维护双方接口的稳定性。
Undo实现
Undo/Redo是很多系统都会提供的功能,实现的方式也很多,就我经历过的系统,有两种相近的做法:
第一种做法是Command对象提供undo/redo方法。当执行Command的时候,将其保存到一个堆栈中,执行Undo/Redo的时候调用相对于的Command的undo/redo方法即可。
第二种做法就是这个项目中的做法,我们在Command执行的时候,创建了新的Transaction,执行完Command以后,Transaction也创建相关的Change,然后提交。提交的Transaction也是保存到一个集合中,Undo/Redo的时候就查找对应的Transaction然后undo/redo。
实现中,Undo/Redo本身也是一个Command,但是是不记录在执行集合中的。
事件模式
这就是个典型的观察者模式,只不过有两个比较有意思的做法:
1. 可以订阅一个类型的所有事件。以前的系统中,我们都是订阅一个对象的事件,这个系统的实现中,我们可以订阅一类对象的事件,这个比较有意思,事实证明也很有用处。实现这样的功能,与实现简单的观察者并没什么不同,只不过发事件的时候多绕个弯,让某些对象特别处理一下就可以了。
2. 所有相关UI都监听Model的事件,然后更新。这是正常的操作,稍微有点区别的是UI的操作都提供了Preview的效果,这个时候修改的只是UI上的数据,最终提交的时候才会真正修改Model,然后Model发事件更新所有的UI。
SaveLoad实现
这个部分也没什么特别,就是访问器模式的应用,实现了将文件中需要持久化的树对象结构存储成二进制格式和xml格式。这个树对象接受一个访问器,这样提供了一个扩展点。
架构反思案例之桌面程序架构案例