基于BREW平台松耦合设计V-USBApp的设计和权衡
毛晓冬 2008-5-7
一、 概论:
本文通过一个新模块USB App的设计,探讨了一些基本的设计思想和原则,同时说明了思想和原则的使用有时必须权衡复杂度。
二、 需求说明:
目前我们手机进入U盘模式的设计是通过进入Setting菜单选择后进入。但是现在主流手机的实现,都是在任意界面插入USB数据线后,自动弹出选择框,供用户选择进入U盘模式。我们的期望也是设计成如此,但同时又是有条件的弹出选择框,即,某些界面允许立即弹出(如IDLE,MainMnu等),某些界面不允许弹出框(如正在下载BREW App时),某些界面需要延迟弹出(比如,正在发送短信时插入USB,需等短信发送完毕后才弹出选择框)。
三、 为何不这样设计:
基于上面的需求,我们最直觉的设计可能是这样的:哪些应用对是否可以弹出USB模式选择框有特殊要求的,那么我们就对他们做特殊处理。可以利用发事件的方式,因为我们知道这些特殊应用的CLSID,根据他们对事件处理的返回值,可以判断当前这些特殊应用是否允许USB App启动。 或者直接调用某些特殊应用提供的API,来完成冲突的特殊处理,比如MP3_Suspend,Mp3_Resume等。
但是这种设计根本不能成为方案,是很糟糕的,为什么?
这是耦合! 模块间必须是透明的,所以冲突处理必须是框架性的,而不是特殊的针对某个模块。否则,特殊模块的变化,会直接导致冲突处理的变化。
向特定的已知的CLSID的App发送事件,或者调用已知的App提供的API。 这种特殊性,使得其不能成为一种框架,甚至是方案,因为无法抗变化,因为是App-Spec的。
试想,当参与冲突的App减少时,可能出现编译无法过,因为CLSID不存在,或者API不存在,USB App需要改。 当参与冲突的App增加时,USB App需要改,因为需要调用新增加的API或者向新的CLSID App发送事件。更何况这种变化甚至允许发生在运行期!
这种不期望的更改的发生,就是由于耦合的存在,导致了变化点无法封装。
四、 应该如何设计:
这里,我们可以看到,变化点其实就是参与冲突的App。 所以,需要把该变化点封装起来,即,不管参与冲突的App的增加和减少,甚至是运行期的增加和减少,以及冲突处理策略的变化 都被隔离, 不影响到USB App。 如何隔离那? 那就是要使得这一 对 多的关系彻底不可见, 即, USB App 与哪些 参与冲突的 APP交互 完全不可见, 只是在运行时动态的绑定。
此时就需要制定冲突的框架,每个参与冲突的模块,自觉遵循该框架(规约),以此,达到相互“完全陌生”的情况下,实现冲突处理。
这个框架是一个一对多的框架,负责把一 对 多的通信间的耦合解除。 所以可以借鉴 Observer 方式。
首先,我们将USB App抽离出UI逻辑和业务逻辑,业务逻辑由IUSBEx扩展接口完成,UI逻辑由USBApp完成。
接着,IUSBEx提供AddInterester 和 RemoveInterester API供冲突感兴趣的观察者 注册 和 取消。
最后,当USB数据线插入事件发生时,USB App会通知所有这些观察者,并根据反馈,决定下一步操作。 由于注册 和 取消是在运行时发生的,所以,USB App和 其他模块只是在运行时才绑定,所以是松耦合。
五、 设计的权衡:
为何不实现成完全的Observer模式,即,单独抽出一个接口,作为被观察者接口 IUSBSubject, 感兴趣的模块向该接口注册, 通知的发生,是由IUSBSubject内部去通知各观察者,并反馈结果给USB App(即整个冲突处理策略完全封装在接口内部),而不是直接由USB App去完成冲突处理。 这是基于复杂度的权衡, 因为系统相对简单,没有必要为了分层而分层,为了使用模式而使用模式。需要知道实现成完全的Observer,没有直接的事件通知和反馈简单。
六、 如何抵抗变化:
由于目前的冲突处理是松耦合的,一 对 多的通信被解耦,并且USB App的UI和业务逻辑分离。所以不管哪个变化点单独变化,都不会影响无关的方面。比如当修改USB弹出界面的UI时,不会影响其他方。 当有新的应用加入冲突处理行列,或退出时,对USB App包括IUSBEx 业务逻辑没有任何影响,不需要做任何变化。
七、 总结:
本文通过USBApp的设计,探讨了应该如何设计,而不该如何设计,以及相应的原因,以此突显一些重要的设计思想。
目前,该设计框架已经运用于后续的项目中。