我对图书借阅系统的一点小小的看法

今天详细看了一个帖子:http://www.jdon.com/jivejdon/thread/39844,该例子是以图书借阅系统作为例子讨论如何进行建模。看完这个帖子感觉真的受益匪浅。下面我也想表达一下我的个人看法。

其实我觉得四色原型和DCI早已经明确给出了建模的思路了。四色原型告诉我们如何分析需求,而DCI则包含了如何将四色原型的分析结果转化为设计,我现在觉得整个转化过程也是相当平滑的。

按照四色原型的思想来分析借书这个场景的话就是:某个人以借阅者的身份从图书馆借书,这里其实还隐含了另外一层意思,就是:书以“被借的书”的身份参与了借书的场景。好了,从这个分析我们可以清晰的看出,借书这个场景包含两个参与该场景的角色:借阅者和被借的书。场景的Execute方法看起来就类似下面这样:
borrowBookContext.Execute(BorrowerRole borrower, BookToBorrowRole bookToBorrow);

其中:
borrowBookContext是一个场景;
Execute是场景的触发方法;
borrower和bookToBorrow是参与该场景的两个角色。

我现在觉得这样的设计有很多好处,比如:
1)由于角色明确表示场景参与者的身份,这就使得整个场景的交互过程非常容易理解,并且我们的代码也会非常具有可读性。
2)从多态的角度来理解的话,角色相当于是一个场景的变化点,或者按照四色那本书上的说法,就是插入点。因为一个角色可以被多个不同的扮演者所扮演,但场景面对的是角色,所以不管角色扮演者是哪个对象,场景中协调各个角色相互交互的逻辑不用改变;从这里也让我想到,当我们要引入灵活性时,往往只需要加入一个中间层(也就是变化点,插入点,或者叫接口,呵呵)就能解决。
3)角色的主要目标是定义场景参与者的身份,而这还提供了另外一个好处,那就是暴露该暴露的,不暴露不该暴露的;角色提供了具有某种身份的场景参与者参与到该场景所需要的最小契约。很多时候,我们不希望某个场景参与者暴露不该暴露的信息给当前场景。而这点在传统的DDD的领域服务中是做不到的,或者说我们根本不会考虑这个问题。

另外,在那个帖子中有一个很有争议的问题就是:图书借阅系统的领域模型中,是否该包含图书卡,是卡在借书还是借阅者在借书?我的很奇怪怎么这个问题都会有争议,我的看法和SpeedVan是完全一致的,图书卡根本不应该出现在领域模型中,图书卡只是一个识别Borrower身份的手段。就像SpeedVan所说,我们不用图书卡用DNA或其他的手段也照样可以识别身份,照样可以进行借书。所以,我们要搞清楚当前的场景是“借书”,问任何一个有基本世界观的人都知道是“人”在借书,而不是卡在借书,当然“人”是打了引号的,目的是表明在我们领域建模的设计阶段,把人做了抽象,抽象为了Borrower了,即图书借阅者角色。但是在系统运行阶段,这个Borrower最终还是会由一个具体的人来扮演。而图书卡最多只是一个用来记录借书历史的工具,而实际上现实生活中的图书卡也很可能只记录了一个卡号而已,所有的借书历史都保存在图书馆的某台服务器硬盘里。所以,我们不能把结果当成是行为的执行者。另外,我们还要明白的是:什么事场景的参与者,什么是场景的触发者。场景的参与者是角色,角色当然可以由人扮演,也可以由其他的对象扮演。但是大家不要理解为是人属于了领域的一部分。我也赞同领域中不应该包含人的说法,人是领域的使用者,不包含在领域内。但角色即场景参与者是属于领域的,角色可以由人来扮演,但那是在运行阶段,并且对场景来说是不知道当前角色由谁扮演的。

最后,我还想说一点就是,之前我一直说我们可以用事件消息的机制(即发送事件和多个领域对象响应事件的方式)来代替场景,并且谈到角色是人类的一个主观认识,不应该放在领域模型中。我现在否定了我当初的想法。原因如下:

事件消息的机制无法实现多态,因为同一个事件,响应它的领域对象是固定的。而有些情况下同一个事件可能会由两个不同类型的领域对象去响应。这点也是我认真研究了DCI中场景和角色以及角色扮演者的理论之后才感觉到的。在DCI中,我们会先在场景外指定好角色扮演者,然后让其扮演某个角色,然后参与到某个场景的活动中。这个过程是一个“聚”的过程,即先搜集一些对象,然后给它们赋予身份(当然赋予身份的过程中可能还会做一些身份验证或条件验证的过程,比如会确定你到底是否允许被赋予这个身份),然后对象以它们被赋予的身份参与到场景中去和其他角色进行交互。而事件消息的过程则完全相反,是一个“散”的过程,是一个分发广播的过程。如果是用这种设计,我们会先定义好一个事件,该事件表示用户要做什么,然后触发该事件,然后事件的响应者会自动响应该事件。所以,现在想来,我其实在想用一种“散”的方式来代替“聚”的方式,这是完全错误的。比如场景的方式下,我们可以控制角色相互协调的逻辑,比如那个角色的哪个方法先调用,哪个后调用,但在事件的方式下我们很难控制;另外一点就是事件的方式下,事件响应者无法实现多态,当我们出现了场景的逻辑一致,但参与者不同时,就只能定义新的事件了。而在DCI的方式下,场景接收的是角色,角色可以由多个不同的对象来扮演,从而可以很好的支持场景行为的复用。因此,我现在才恍然大悟,就是所谓的事件,是只能用于“通知别人我做了什么”的,而不能用于“通知别人做什么”的。因为很多情况下你无法知道别人是谁,或者更直接点就是你不知道你要通知的对象的类型。

另外,我记得flyzb还提出过,DCI中,如何实现场景复用的问题。我觉得这个在DCI中不是问题,因为DCI本质上已经实现了场景的复用。场景中面对的是角色,而角色是一种抽象的身份,不代表任何具体的对象。而场景实现内发生的逻辑就是由这些抽象的角色相互协作产生的,那难道不是已经很好的实现了场景的复用了吗?在我看来场景的本质就是某个活动,或者说是某些角色相互协作所产生的一种交互活动。
[该贴被tangxuehua于2011-05-21 20:11修改过]

2011年05月21日 18:51 "@tangxuehua"的内容
borrowBookContext.Execute(BorrowerRole borrower, BookToBorrowRole bookToBorrow); ...

最好把具体实现的execute伪代码也填一下,很多时候,一段伪代码抵万言啊。

看了半天,不知DCI具体为何物。如果按照Data对应领域模型,Context对应用例实现,Interact对应于角色交互来看。那么,最起码,这种分析方法赋予用例太多任务了。
用例本身作为捕获需求的半形式化工具,其根本任务就是重视捕获需求过程,这一过程使得我们更好的认知问题域中的复杂业务处理流程。重现捕获需求如此重要,以至于该过程的结果——用例本身都显得不太重要了。这样DCI把关注重点放在这种主观意识理解的产物——用例身上,势必需要对用例赋予新的责任。比如用例要比较稳定,不能时常修改,而认知陌生事物过程本身就是一个由浅入深的过程,所以用例不可能一步到位,不做修改或者删减;比如用例中的内容要便于软件实现,势必掺杂代码思维,而弱化业务需求本身……

以上是我对DCI肤浅认识,现在看来,需要一个DCI实现案例,做具体分析。我说的案例当然包括用例编写过程,而不是涉及到用例只是一张用例图简单带过。