以JiveJdon案例说明对象职责和SOLID原则应用
最近我和oojdon讨论给帖子加上浏览阅读次数这个功能,起初我们并没有从职责角度来考虑阅读次数这个功能,就简单地在Service中获得Thread方法时,添加一些代码,用来统计次数。
因为我们这时重点是如何用Domain Events来实现阅读次数持久化问题,也就是说,阅读次数并不是每次阅读就保存持久化一次,而是增加内存中计数,持久化保存是每隔60分钟保存,阅读次数这个数据并不是一种状态,不会对论坛功能产生重大影响,因此,我们没必要紧张兮兮每次保存。
从这里我们可以看出,持久化问题还是在OO设计中占据主要精力,就忽视了其他更重要的考虑,当然,目前考虑是是否需要持久?比以前考虑如何持久这个战术问题已经进步很多。
忽视的重要问题是:我们考虑持久,实际过度关注了对象中数据和属性,忽视了对象和单纯数据主要区别是对象有行为,行为职责才是对象根本点,和首要考虑的。
那么造成什么问题呢?就造成我们将阅读次数计数这个功能放到了Service中,表面好像是阅读次数数据和次数计数行为分离了,类似桥模式,实际这是一种面向过程的非OO设计,这种情况大量存在Spring框架的设计中,很多人不知觉罢了。
DDD告诉我们Service中是放一些不属于领域对象的功能行为,所以,我们从一开始就没有抓住重点:阅读次数计数这个功能是否是领域对象ForumThread的职责?
如果我们一开始分析需求时,从面向对象分析角度来分析,根据“对象职责模式”,抓住对象行为这个根本点,那么我们的重点就是在考虑:阅读次数计数这个功能是否是领域对象ForumThread的职责?
答案是肯定的,既然是领域模型的职责,那么就要成为其方法,所以,阅读次数计数应该是ForumThread的行为,这个功能应该在ForumThread中实现。
考虑对象的职责,就要用命令 触发 事件 状态 监视 等行为模式的思路来考虑,在Service中getForumThread方法中,需要调用这个ForumThread中的计数方法,调用者实际是一个客户端或者说命令源泉,是命令command的触发点,有了这个认识,我们就可以再进行考虑:这个命令触发点放在这里是否合适?阅读次数应该是和界面有关,放在Service中,如果是其他内部功能调用getForumThread方法也会触发计数,显然不妥,所以,这个触发点应该放在表现层界面层,至于是用AJAX和Action来实现,属于战术问题了。
分析到这里,我们看到:对象职责和DDD结合是非常重要的,如果不抓住对象职责模式,我们就可能走到面向过程编程;相反,如果一开始考虑持久问题,无论是否要持久以及如何持久问题,因为持久的只能是数据,所以,就会引导我们走向面向数据库面向过程老路,千里之行,始于足下,第一步决定方向,很重要。
当我们完成职责发现和分配以后,这时SOLID原则就跳出来对我们进行指导了,首先是单一职责问题,ForumThread中已经有addNewMessage等职责,现在我们加入阅读次数计数职责viewCountAction方法,很显然,这两种职责不属于同一种,不满足单一职责,如果我们将这两个方法抽象重构到接口中,就成为:
|
这个接口的命名你都觉得困难,我这里暂时使用ForumThreadIF,这是不准确的,因为这两个方法无法用统一行为来命名,实则因为它们就不是同类别,道不同不相与谋,按照SOLID原则,应该划分到两个接口中。
那么ForumThread这个领域模型就实现两个接口,当然,如果考虑除以上两个职责,ForumThread还有更多职责,那么就要有更多接口,最后,ForumThread就成为实现多个接口的肥胖的大类了,这类似SOLID原则一文施乐公司的那个大JoB类,绕了半天,肥胖大类又出来了。
其实在这里,我们陷入一个误区,我们可能把屁股当成脑袋了,ForumThread这个肥胖类中的这么多职责是我们运行JiveJdon软件时的结果,也就是说,在JiveJdon运行时,ForumThread必须是一个肥胖“对象”,拥有有这么多职责,但是这并不意味着,我们在设计ForumThread就一定要搞一个肥胖“类”,我们可以将ForumThread切分成多个类包括接口(用边界封装它们),在JiveJdon运行时,再组合成ForumThread肥胖对象。
这实际是 DCI架构是什么?中一部分含义,AOP/Mixin、面向函数语言以及动态类型语言都可以实现“在运行时刻,根据角色场景,动态组合生成ForumThread肥胖对象。”
我想:这个美好的设计架构就是下一步我们需要的。编码和运行阶段分离将是目前我们一直孜孜不倦追求的。
[该贴被banq于2010-02-27 10:02修改过]