基于BREW平台松耦合设计IV-重构AlarmService的整体设计
来源:互联网 发布:linux 扩大磁盘空间 编辑:程序博客网 时间:2024/05/12 04:54
基于BREW平台松耦合设计IV-重构AlarmService的整体设计
毛晓冬 2008-5-6
一、 概述:
本文主要通过一个实际模块的改善过程,感性的体现设计原则的使用。
二、 目前的AlarmService框架:
目前的AlarmService主要包括两个模块,AlarmApp,TodoApp。AlarmApp主要负责闹铃(包括自动关机,关机闹铃)功能,TODO主要负责类似日程提醒的功能。随着需求的增多,目前这两个模块其实比较复杂。目前的整体设计框架如下:
AlarmApp负责UI的显示,Alarm的设置,过期处理,计算下一个Alarm,存取Alarm的持久性数据。
TODO和AlarmApp类似。这里可以看到,AlarmApp和TODOApp模块内部直接完成了所有的逻辑。
我们再看看模块间的交互(按上图的序号):
1. 日程提醒到期后,TODOApp通过发送Event给EventBox,由EventBox来弹出日程提醒框。
2. SettingApp中可以设置自动关机闹铃,SettingApp通过发送Event给AlarmApp,由AlarmApp来最终设置该自动关机的闹铃,使得闹铃的控制完全由Alarm负责。
3. 闹铃到期后,AlarmApp通过发送Event给EventBox,由EventBox来弹出闹铃提醒。在EventBox界面,当用户选择Snooze或者关闭时,EventBox再发送Event通知AlarmApp应该Snooze还是关闭。
4. 当AlarmApp中不存在Active闹铃时,AlarmApp会发送Event给TODOApp询问是否TODOApp也不存在Active的日程。TODOApp通过回Event的方式告知结果。如果TODOApp也无Active日程时,AlarmApp发送Event给CoreApp通知清除系统栏的闹铃图标。 当TODOApp中不存在Active日程时,处理类似。
5. AlarmApp使用BREW的AlarmService提供的API。同时,BREW以Event方式通知AlarmApp闹铃的过期。
6. TODOApp通知CoreApp清除闹铃系统栏(见4)
7. TODOApp使用BREW的AlarmService提供的API。同时,BREW以Event方式通知TODOApp日程的过期。
8. AlarmApp通知CoreApp清除闹铃系统栏(见4)
三、 目前框架的缺点:
1. 紧耦合:这里存在两个紧耦合,一是模块内的紧耦合,一是模块间的紧耦合。
a. 模块内的紧耦合:AlarmApp/TODOApp应用代码内部完成了包括UI显示,设置Alarm,日程,处理过期,存储数据等等的所有任务。整个显得较乱。导致某一方面的变化(更改,比如UI的)影响到整个应用,不得不更改整个应用。牵一发而动全身,稍有不慎,无关联的方面也会受牵连,出现问题。不仅如此,模块内的紧耦合还导致代码逻辑混乱,不利于维护,更加不利于新人的阅读和掌握。
b. 模块间的紧耦合: 如上面的框图,可见多个模块间直接使用Event方式通信,在编译期就绑定在了一起,呈现出网状(多 对 多)的耦合。这种模块间的紧耦合,导致单个模块的变化可能不期望的影响到其他模块,并且也使得代码逻辑混乱,不利于维护,也不利于新人的阅读和掌握。
2. 低复用:其实TODOApp和AlarmApp两个模块除了UI的差别比较大外,内部的逻辑处理(如何设置alarm,如何处理Alarm到期等等)以及持久性数据的存储都基本一样,完全可以复用。但目前的方案下,完全由两个模块重复的独立自己实现一套。
3. 不易扩展:扩展性是和耦合性密切相关的,耦合的如此紧密,那基本上就只能是“修改”,而不是扩展了。
4. 难以抵抗变化:需求往往带动变化,永恒不变的就是“变化”。所以,一个设计的好坏,就是看它是否能抵抗可预见的变化。变化只有被各自独立的封装起来后,才有可能抵抗变化,因为此时一个变化点不会影响到另一个不变点。而目前的设计,模块间和模块内的紧密耦合,根本无法抵抗变化。UI的需求变化,导致直接更改整个App,业务逻辑的变化,也导致更改整个App。
四、 如何逐一改善:
1. 利用三层结构解决模块内的紧耦合:
从上面的分析可以看到,模块间的耦合主要是由于把所有逻辑都混在一起了,没有分层处理。当进行分层处理后,就可以区分不同的逻辑,进而就可以封装并隔离变化点,让每一部分的变化独立的进行。
以抽象的观点来看AlarmApp或者TODOApp模块,存在3个独立的变化点,UI,内部逻辑,数据存取。由此,我们可以将其分解为UI逻辑,业务逻辑,数据访问三层,或言之3个变化点。分解之后,就是封装,封装之后,各个变化点的变化就不再会影响其他部分了。
这里UI逻辑以应用形式实现,即App只负责UI。
业务逻辑以扩展接口独立,IAlarmEx,负责AlarmService相关的业务逻辑,内部访问数据访问接口,对外提供:获取Alarm状态,设置Alarm,Alarm过期后的自动处理,Snooze处理等等
数据访问以接口独立(目前使用IDataBase)负责持久性数据的存取。
2. 利用中介者(Mediator)方式解决模块间的紧耦合:
原有的模块间都是直接交互(事件方式),体现出网状。这是紧耦合的根源。
可以试着引入中央控制层,所有模块都只和中央控制层交互,由中央控制层“代劳”与目的模块交互。从而,将多 对 多的关系, 简化为 多 对 一的关系, 便于控制。 这种降低多 对 多之间的耦合方式, 其实就是 中介者(Mediator) 方式。
当将IAlarmEx设计成一个公共接口提供AlarmService的业务逻辑时, IAlarmEx就是一个中介者。 所有的模块,现在都只和IAlarmEx接触,IAlarmEx内部直接消化相应的业务逻辑处理,除非必要时,IAlarmEx才会和其他目的模块交互。
3. 解决低复用:
由于IAlarmEx是一个公共的业务逻辑接口,TODO 和 Alarm基本只是UI的差异,所以, TODO可以完全复用IALARMEx接口。
4. 解决可扩展:
由于分层,封装了变化点,使得扩展性增强了,即,在不改变原有整体代码的基础上,扩展功能。
5. 数据的透明传输:
原本的某些模块间传递特殊的App-Spec 数据,比如AlarmApp需要将当前到期的AlarmID,铃声文件路径等信息传递给EventBox,TODOApp则必须把日程内容传递给EventBox。这些数据,都属于UI逻辑范畴,进一步讲,属于通信的端点的范畴。必须对中介者,或者说业务逻辑而言是透明的,否则,随着通信端点Spec的变化,或者UI逻辑的变化,就会使得中介者,或者业务逻辑也产生不期望的变化。为了隔离这种变化,我们引入了数据的透明传输,即IAlarmEx接口提供设置Alarm,或者Add一个日程时,API的最后一个参数为VOID*的透明数据,IAlarmEx只负责存储该透明数据,并当Trigger时,把该透明数据传递给Destination,比如EventBox,至于究竟是什么数据,一无所知,在通信的发,收方,完成数据的封装和提取,其实就是一个VOID*向特殊指针的转换。其实,这和OSI的7层协议很类似,底层接口向上层接口服务,并且完成数据的透明传输。
6. 使用Adaptor:
我们分层后的设计,其实和平台是无关的。但是,因为我们运行在具体的平台上,所以往往可能出现平台所期望的接口和我们设计的接口不一致,此时我们就需要一个Adaptor。
在我们的新的设计方案中,一切业务逻辑都是在IAlarmEx中完成,包含Alarm到期后的处理,完全不需要和其他模块相关联。但是,BREW平台的AlarmService必须存在一个App的载体,因为其设置Alarm时,必须指定App的CLSID,即,Alarm是为App设置的,而不是接口。同样的,当Alarm到期后,BREW通知的是App,而不是接口。
不过不要紧,这种差异性,我们只需要引入一个Adaptor即可。基于BREW的限制,该Adaptor必须是一个App。因为,我们同时设计了一个AlarmDaemon App,主要就是完成Adaptor的功能,即,IAlarmEx的实现中,当设置Alarm时,使用AlarmDaemon的CLSID(将其作为载体)。Alarm过期后,AlarmDaemon App收到通知,此时其处理非常简单,直接调用IAlarmEx_Trigger API即可。通过AlarmDaemon的Adaptor功能,使得其他任何模块,都没有被关联进不该关联的业务逻辑中来。
五、 重构后的新框架:
基于前面的描述和上面的框图,大家应该对重构后的新方案比较熟悉了。这里再说明一下,基于重构后的新方案,原有的场景,现在变得多么简单(按序号): 1. 日程提醒或者闹铃到期后,IAlarmEx_Trigger被调用,内部计算下一个闹铃并设置后,就会自动的通知EventBox,并传递相应的透明数据。EventBox弹出提示框。当用户选择SNOOZE时,EventBox调用IAlarmEx_Snooze即完成了Snooze闹铃的设置。 2. SettingApp通过调用IAlarmEx_SetAlarm来设置自动关机闹铃,同时可以调用IAlarmEx_SetSnoozeMode来更改Snooze Mode的Value。 3. AlarmApp通过调用IAlarmEx_GetXXX来获取Alarm数据并在UI上显示,当用户设置,取消Alarm时,调用IAlarmEx_Set/CancelAlarm。 4. TODO和AlarmApp类似。 5. IAlarmEx内部使用BREW的AlarmServiceAPI 6. 对于自动关机闹铃到期后,IAlarmEx内部通知CoreApp并传递透明数据,CoreApp实现关机。此外,当IAlarmEx监测到没有任何Active TODO和 Alarm存在时,则会通知CoreApp去除系统栏的Alarm标志。 7. AlarmDaemon作为Adaptor App充当载体,接收BREW AlarmService的通知,并委托给IAlarmEx处理。 8. Alarm到期后BREWAlarmService通知AlarmDaemonApp 需要注意的是,框架中的IAlarmEx主动和其他模块间的交互,是通过事件的方式。但却不是在编译期绑定的,即没有使用硬编码的CLSID,而是使用了BREW的MIME Handler机制,在运行时动态的绑定,从而解除了通信时的耦合。 六、 如何抵抗需求的变化: 逻辑被分离了,变化点被封装了。从而可以抵抗需求的变化。比如: 新项目对UI的要求较高,此时只需要更改UI逻辑即可,完全不会影响到业务逻辑。 新项目要求支持关机闹铃,并且支持时间可设置,此时只需亚更改IAlarmEx,即业务逻辑,完全不影响应用。 新项目要求EventBox显示闹铃时更加个性化,显示的风格可以定制。此时,只需要更改AlarmApp和EventBox的“通信协议”,由于IAlarmEx使用了数据透明传输,所以此时并不影响IAlarmEx,即不影响业务逻辑。 新项目要求使用新员工接手该模块,不用怕,新员工2天内就理顺了代码 J 七、 总结: 本文通过对现有的AlarmService的框架提出缺点,并且讲解如何一步步解决问题,最终重构得到一个新的框架,感性的摄入了一些重要的软件设计思想,希望对大家有帮助。 注意:本文仅从理论上描述了一个重构的方案,至于新方案是否会在新项目或者新平台中使用,这个和具体的实施进度,项目进度有关。目前来看,由于前期的实现,验证,移植尚未完全完成,所以在6055上使用的可能性也并非100%。
- 基于BREW平台松耦合设计IV-重构AlarmService的整体设计
- 基于BREW平台的松耦合设计III-可移植、可复用软件的设计原则
- 基于BREW平台松耦合设计V-USBApp的设计和权衡
- 基于BREW的松耦合设计初探
- 基于BREW的松耦合设计再探
- 基于Hadoop的大数据平台实施记——整体架构设计
- 基于Hadoop的大数据平台实施记——整体架构设计
- PHP的耦合设计模式
- 基于.net的可扩展框架设计 - 整体架构
- Xen 的整体架构和整体设计
- 松耦合和紧耦合的架构设计及性能对比
- 松耦合和紧耦合的架构设计及性能对比
- 基于.NET平台的分层架构设计
- 基于OPenStack平台的C++ API 设计
- 界面整体效果的设计
- SoftwareModel - 避免耦合的设计原则
- PHP耦合设计模式的理解
- BREW的事件机制,BREW中的设计模式
- JQuery Ajax调用asp.net后台方法
- QFtpLib
- git学习--object database
- C语言之可变参数问题
- MyEclipse项目无法自动编译解决方案
- 基于BREW平台松耦合设计IV-重构AlarmService的整体设计
- $HTTP_POST_VARS['']和$_POST['']的区别
- 基于BREW平台松耦合设计V-USBApp的设计和权衡
- DBHelper:三层架构的高级实效的后台数据库操作类的写法或者叫用户帮助类
- 反反复反复
- 锁相环
- ubuntu 从硬盘启动安装
- 孟岩:什么是高级C++?
- Java 类构造顺序