gtalk上一位道友问到的一些与四象图有关问题,部分记录如下:

模型,包括网络、系统、场所(空间)、实体。

模型,可以mixin多个行为特征和结构特征,多重继承也可以用来表达凝聚多个特征,尽管它非常笨拙,且难以扩展。

行为特征,比角色的词义要广。

比如,
模型是“互联网”时,其结构特征可能是符合“小世界”理论;行为特征可能是符合“幂律分布”或“长尾分布”。

模型是“交际网”时,其结构特征可能是符合“六度分离”理论;行为特征可能是符合“马太效应”等。

结构特征与行为特征并非一定显然易见,很多时候(比如模型是复杂网络时),要发现并证明它其特性(特征)时是非常困难的。

1.既然用Aggregate来类比为角色很勉强,那么我也希望你不要随便做类比吧,不然对DDD或四象建模还不太深入了解的网友会很容易被误导;
你说:行为特征(borrowedBook),在图书管这个例子中,表达了交互的事实,“谁”何时“借(还)”了何“书”
这个我也不赞同,如果borrowedBook是行为特征,是事实,那么至少这样命名就不合理。borrowedBook任何人一看都认为是一本“被借的书”,当我们看到这个名称时首先想到的是它是一样东西,而不是事实;事实就是历史,表示发生了什么,发生过什么。“某本书被借了”,这才是事实。所以事实是不可能更改的,因为事实就是历史。但如果像你所说,borrowedBook是事实的话,那为什么它的状态特征如borrowedTime,returnedTime还能被修改?我觉得你心中认为uda1341的面向事实编程不错,但实际上你还没真正理解什么是面向事实编程;

2.每一本书的数量信息是需要记录,但决不应该由书本来实现这个记录功能;书本本身很简单,描述了书本的本质属性,但是书本自己怎么会知道它还有多少本跟它一样的书在图书馆?任何正常思维逻辑的人都清楚如果我要查询某本书现在在图书馆还有多少本?首先第一反应就是去问图书馆,也就是说通过图书馆查询这本书还有多少本。我之前已经说了,如果这本书被借走了,但是这本被借走的书却还携带一个信息就是它目前在图书馆还有多少本库存,这不是很奇怪吗?图书馆具有管理书本的职责,因此它有能力也应该知道每本书的库存;

3.我并没有说你某些属性提供set不好,我说的是你把所有的属性都提供了set,这就不妥了。有些属性就是在对象创建后不能再被更改的,这是该对象对外界表达的基本规则;比如borrowedTime这个属性,为什么还要运行别人改?即便你说对象的属性是否运行被修改是由业务场景决定的。但你觉得这样合理吗?一个对象在业务上是否安全需要由别人来决定,那这个对象还是一个独立的,具有交互能力的对象吗?如果你觉得这样是合理的,那么当别人看到你设计的对象提供了set的方法,他可能就会认为这个属性在任何场景下都是可以被改的,起码这个对象告诉了别人说,你们可以修改我的borrowedTime属性,即便我已经被创建了。更何况你说borrowedBook代表了一个事实,那事实怎么还会暴露可被外界修改的set方法呢?

4.之前你和SpeedVan争论的最激烈的莫过于Card是否应该包含在领域内的问题。他认为Card不应该在领域内,在领域内的应该是Account;而你认为Card必不可少,我记得你们还举了个杯子喝水的例子,等等;但是你现在在你的领域模型中去掉了Card,而把Account包含了进来,我想确认的是现在的你的想法是不是和当初的SpeedVan一致了?是还是否?

5.你的回复中说到“Account关注的基本事实,是其借了哪些本书,还了哪些些书,borrow和return只是记录这个事实而已。”,看到这话,让我对你的思考问题的思路很奇怪。任何人都知道,borrow和return是行为,是交互行为。而行为的意义不是用来记录某个事实,行为是先于事实而存在的,只有行为发生过了,事实才会产生;我们思考问题的角度应该是行为如何交互,谁和谁交互的问题,而不是事实如何记录;我觉得还是uda1341的面向事实编程影响了你。按照你这个观点,那么整个系统的交互行为都变成是我们如何记录事实,而不是我们如何思考对象之间的交互协作。我再强调一遍:事实就是历史,事实不可改变,事实包含的信息可多了,包含整个交互场景的整个过程以及所有参与对象,以及交互后所有受到影响的对象的状态的总和;borrowedBook只是借书这个交互行为借书后的一个产物,但是这个产物一定不是一个事实,而是一个新的对象,之所以会有一个新的对象产生,是因为我们从此会关注它,关注它的状态,关注它何时会被还到图书馆;如果你说一笔借书记录、还书记录、转帐记录是一个事实,那我还可以接受。

6.借书和还书就像你所说是两个对立的场景,但我觉得你的理解也有点问题。从对象的角度来说,首先借书的过程是book 转变为 borrowedBook的过程,这个过程结束后,book应该从内存中消失了,取而代之的是borrowedBook从内存中诞生;而还书的过程则正好相关,也就是说borrowedBook会从内存中消失,而book又会重新从内存中诞生。所以,当“被借的书”被还了后,都从内存中消失了,为什么还要把还书时间记录在borrowedBook上?存放还书时间的应该是“还书记录”,即某次还书的一次历史记录,通过Log的方式来记录;

最后,你说“使用DDD,DCI, 彩色UML作类比,是因为我以为,在这里这些是对大家“更友好的术语”,“更容易接近我要表达的东西”,但也有可能让读者理解起来,浅尝辄止,甚而背道而去,非我所言。”,那么我觉得你还是不要这么简单的就做类比,既然类比了,就要把类比的原因说出来,否则别人很容易产生误解。因为我对DDD,DCI,四色原型,还有你的四象建模一直在关注,也许没掌握好精髓,但至少知道是怎么回事,所以才会给你提出这么多疑问,如果一般人(没有相关知识背景的人)看到你花了这么多心思写出来的理论和例子,我相信也不会给予任何有针对性的不管是赞同的还是批判的评论的。

既然你意识到了别人可能会误解你的观点甚至是背道而驰,那么还需要您好好努力,在理论上希望能更加让大家好理解,我希望是理论和实践都有实质性的东西出来。而不是纯偏理论但实际例子却很缺乏,或者例子还会让别人产生很多疑问的情况;

比如你一直强调模型会凝聚状态特征和行为特征,那你有提出过相关的实现方案吗?我要求的不只是“通过应用什么样的设计模式来解决”这样简单的解决方案,而是需要更具体的有现实意义的解决方案。

最后还有两点我觉得不妥的地方:

1)我不觉得状态是被凝聚的,这个说法本身就有点问题。我认为状态只可能是变化的,不可能凝聚到某个对象上。比如你举的一个例子:
花木兰披上军装行将军之职。这句话你说花木兰披上军装是凝聚状态特征的一种表现,但我认为不是。我觉得这是花木兰的“衣着”状态发生了改变,她本来是穿普通衣服的,但是当她出去打仗时,穿上了军装。所以,在我看来,这只是花木兰的状态在发生变化,而不是凝聚了一个新的状态;在我看来,一个对象的状态永远是固定的,不可能增加或减少,因为既然叫状态,就意味着和这个对象生成之时就一起具备的,只是随着时间的推移,这个对象的某些状态会发生变化,比如人的身高、体重这些状态总是会在一定阶段时不时的变化。另外,对象除了会暴露状态这些固有的信息外,还会关联一些“关联信息”。通常对象的关联信息都是在对象参与到交互互动后才会具有,比如一个人去参加某次英语六级考试,这是一个交互活动,如果这个人考试通过了,那么他将拥有一本英语六级考试通过的证书,那么这本证书和这个人的关系就是关联关系;也可以理解为一种状态,但最好不要这样理解,因为这很容易让人把对象的固有状态混淆。再比如借书的例子,一本书被借了后,他也多出了两个关联信息:1)借书人;2)被借时间;总结,对象的信息有固有状态和关联信息两种;固有状态随对象创建后就一直存在,只不过会时不时变化,而关联信息是对象参与交互活动后产生,会以各种方式与该对象进行关联;

2)行为也不是凝聚的,而应该理解为是否表现出行为;比如一个人,他同时具有A、B两个行为,但有时他只表现出A行为,没表现出B行为。但是你不能说他要表现出A行为的时候去凝聚了A行为,凝聚给人的感觉是附加或增加的意思,而对象的行为实际上是表现和隐藏的关系。

不好意思,说了这么多让你觉得不开心的话。我也是经过很多思考后把自己的心里话说出来的,我是个对事不对人的人,如果你的理论确实让我从心里感到佩服,那我一定会双手赞成并高度评价,但如果还有一些让我觉得不妥的地方,也一定会经过思考后提出来。希望你不要见怪!
[该贴被tangxuehua于2011-09-12 17:13修改过]

1、
借书的时候,要填写借阅时间吧, 填错了,可以修改吧,漏填了,还可以补上吧,这都需要setter。
软件是现实的映射,对现实的记录(不管是过去的,还是正在发生的),现实变了,记录也要随之变。

不是不了解DDD的人会被我误导,而是了解DDD的人会自我误导。
一无所知的状态下,更容易理解四象图,因为四象图实在非常平常。稍微注意、提炼自己平时看生活出现的各种情景的方式即可。

历史可以理解为由事实组成,因为历史只是世界的一部分。

此外,在与uda1341交流时,我曾说过,四象图不是OO建模,甚至不是建模本身,四象图更多关注的是语言的组织(原语,通过组织语言来组织信息,进而建模),而不是语言本身(比如OOP, FP,SP, FOP等)。

2、
JiveJdon中有一个类叫ForumState,记录了论坛里发了几个帖子等信息,可理解为论坛本身的计数器。

每一本书的数量信息不是由书(book)来记录,而是由书的计数器(bookCounter)来记录。

值得再次强调的是,没有本质。如果非要说本质(最常见的作用),那么书的本质,“可能”是在于记录信息,传递信息。

3、
模型是自由的,场景规约表示在场景中对其进行必要的约束。

你几点上班、几点下班,都是有场景规约(公司的规章制度)指定的,不是由你(模型)决定的。

setter不代表“不安全”。对外界的暴露程度,如果有需要,可以通过private, protected, package,public,等访问权限控制修饰符来调整。


4、
Account和Card都是工具(前者是后者在软件中的映射),如果Account/Card有其他用途,可将其身上借阅功能剥离出来,放在Book Borrower这个角色上。

之前的讨论我也忘掉差不多了,那个帖子中我最最强调的一点是“无之以为用”,其它的都是旁枝末节。

说些题外话,前段时间,我就删掉了自己一字一字敲打下来的数万笔记,虽然有点舍不得,但心一狠,shift+delete。

旧的不去,新的不来,为了更好地接触一些新的有趣的东西,惟有如此。所以,别怪我,自己写得东西怎么都记不清了。

挺感谢以前的老大,鼓励我将自己的思考记录整理出来,与同事们分享,原来我的习惯只是找一个人聊聊,就完事,不习惯与太多的人进行交流。


5、

正在发生的是事实。行为结果是事实,行为过程也是事实。
一切都是事实,过去的、现在的、将来的,不变的,可变的都是事实。
你不妨重新思考一下,维氏的原话。

事实是什么?事实是信息,不是物质(事物、对象),不是能量(运动、过程),而是在它们之上携带的可感知(有趣或有用)的信息。

软件中,我们关注的不是物质本身(书),也不是运动过程(被借走的书),而是物质与运动携带的信息。

我们定义Book只是描述某书的一些基本信息,用BookCounter描述某书总有几本,还有几本;用BorrowedBook描述此书何时被谁借走等信息。


6、
借书与还书是相对“独立”的场景,因为它们常常发生在不同的时间段。

将借、还时间都放在borrowedBook上,是因为关注其整体,比割裂它们更有实际意义。


7、
DDD、DCI、彩色UML与四象图并不是毫无关系,它们至少是我提出四象图的一些参考资料。

从oojdon那张图,我认为他理解了我的意思;gtalk上有位道友,我认为他也基本上理解了我的意思。

没有这些背景知识,可能更容易理解四象图。

凝聚特征一般的方法非常简单呀,传参赋值即可。

为什么你要想成那么复杂呢?我平时写代码,几乎感觉不到它的存在。
比如写Android UI代码时,实现事件监听器,传给控件,即凝聚行为特征呀。

在动态、凝聚多个特征时,才可能需要引入设计模式来解决。


我不知道为什么:
一提MVC,就需要MVC框架;
一提DCI,就需要DCI框架;
一提赋值就需要IoC、AOP框架;
在中小规模的应用开发中,根本不需要任何框架。
(框架未必能降低复杂度)

只要代码遵行MVC、DCI的组织方式即可,
比如JSP只管视图渲染,Servlet只管行为控制等等。

虽然我自信对于设计模式(不止GoF的23个模式)的理解,比不少工作多年的人都要深刻得多,但我几乎不用(刻意使用),除非有必要,或自然而然。


8、
当一个人老之将至时,脑海浮现出过去的一个个画面(场景)。

当他参加舞会时,穿着漂亮的礼服、拿着话筒(凝聚结构特征),担任主持人(凝聚行为特征),
这些特征离开舞会时,就可以没了。

当他去超市购物时,推着购物篮(凝聚结构特征),充当顾客(凝聚行为特征),这些特征离开
超市时,就可以没了。


结构特征表示模型在场景具有的“形”方面的信息;行为特征表示在场景具有的“神”方面的信息。

场景规约,如舞会的一些约定,超市的一些守则等。

一切都在变化中,四象图的概念都与时间有关。

场景规约,如果是类似于F = ma, E = mc^2之类的普遍法则,可理解为与时间无关,具有永恒性。

第1点

2011年09月12日 23:38 "@jdon007"的内容
借书的时候,要填写借阅时间吧, 填错了,可以修改吧,漏填了,还可以补上吧,这都需要setter。
软件是现实的映射,对现实的记录(不管是过去的,还是正在发生的),现实变了,记录也要随之变。 ...

不管是错填和漏填,借阅时间是一个事实,没有说因为忘记了借阅事实就会被改变,或填错了,事实就被歪曲。“借阅时间”这一属性可被改变时,则它只是一个被人认为的“借阅时间”信息,而非事实。

第2点,基本同意

第3点,关于setter这不需担心,暴露问题可通过接口来隐藏。setter代表可改,即有态

第4点,看来真正理解passport和user区别的人并不多,或者说过去被username所误导的人也很多。或者其实很多人都知道有问题,只是不愿怀疑自己的过去罢了。

第5点,“发生的事情,即事实,就是诸事态的存在”。未来是发生的事情吗?

第6点,书本身是一个事实,借还也是事实,至于事实,怎么分割合并,则看对领域的理解。

其他没什么异议。

2011年09月13日 10:09 "@tangxuehua"的内容
我想确认的是现在的你的想法 ...

花了点时间浏览一下过去的帖子。以明确回答你的问题。
我的基本观点没有变过,用户使用杯子盛水,用户使用图书卡借书。

现实中的Card在软件中可被描述为Account。
不是去掉,而是使用了更常用的说法。

相似的是,银行卡Card在软件中也可描述为Account。

现实中,用户(User)是借书卡(Card)的使用者。
软件中,用户(User)是帐号(Account)的使用者。

不存在Card或Account是用户的映射之类的观点(比如JiveJdon,我们使用Account发帖,Account是工具,也不是User的映射),即便分离出BookBorrower这个角色来,也不是User或Reader的映射,是刻画Card/Account的行为特征或功能。

2011年09月13日 11:13 "@jdon007"的内容
现实中,用户(User)是借书卡(Card)的使用者。
软件中,用户(User)是帐号(Account)的使用者。 ...

不知你此语何解。

说现实,用户是借书卡的使用者。这个用户是我观点中的“驱动者”,这是用户面对系统,甚至对功能而言,而非领域而言,即我所提到的,进入领域的前提(关卡)。

说软件,用户是帐号的使用者。这个我就觉得有问题了,甚至觉得是一种模糊。帐号是成为User,Reader,Publisher的通关口,是在即现在各大门户网站都用到的通行证Passport,jivejdon里的account是把passport和user的混合体。成用户,却使用帐号,何解?在我看来jivejdon的account一直是扮演两重身份。

以下是我的分析:
1)(现实)用户,即“驱动者”,通过passport,将领域中某User和该入口对应,即认为该入口发出的命令或产生的事件,都与该User“有关”,我们可以把这种“有关”设定为“发出者”。

2)(现实)用户,所做的一切乃系统级别,即功能,非领域范畴。

3)(领域)User,是现实的镜像(可以是虚假的,不明白可以以网游为参考),产生过程只在注册(创建)过程。

4)(领域)User,即“参与者”,是逻辑中的对象,(现实)用户与逻辑没有半点直接关系,这就是他们根本区别。

我们用Account来发帖,是一种模糊,现实拥有Account的“我们”与参与发帖逻辑的“我们”不是相等的。“我们用Account来发帖”只是说明了:现实的我们用Account得到发帖的权限,而不是现实的我们参与到逻辑中去,参与逻辑的是与Account绑定的User,甚至这个User与现实的“我们”半点关系也没有。为了避免与传统的Account混淆,所以很多地方都通行证Passport来区别。到这里还不分清BookBorrower到底是对Reader而言还是Passport而言,那我就没话可说了。执着于Account,在我看来只是坚持Account的两重身份而已,Account两重身份用于表述,不是模糊,是啥呢?
[该贴被SpeedVan于2011-09-14 15:09修改过]

2011年09月14日 14:35 "@SpeedVan"的内容
到这里还不分清BookBorrower到底是对Reader而言还是Passport而言,那我就没话可说了。 ...

User指的是系统的使用者,Account在系统中的是一个模型,在图书馆系统中可用来借书,在银行中可用来取钱。

如果Account既可以借书,也可以取钱,甚至可用来发邮件、发贴子。那么可以将借书、取钱、发邮件、发贴子等功能,从模型上单独剥离出来为各种相应的角色或表示为行为特征(比如,图书馆中,剥离为Book Borrower),在借阅、取钱、发邮件、发贴的各个场景中,Account凝聚相应的行为特征,完成各种任务。

我还是坚持我的观点(无之以为用,参见《领域驱动设计之我见》的第一个帖子),不再解释。

补充一些思考,一部分与场景规约有关。

场景规约的设计可能非常简单,对现实进行拷贝即可,比如例子中的图书借阅规章制度;也可能非常复杂,需要权衡很多因素。

事件可分为4类:
0)界面与业务逻辑层之间的交互事件。
1)业务逻辑层,场景之内的事件(用于激活模型,发挥作用,扮演角色)。
2)业务逻辑层,场景之间的事件(用于切换场景)。
3)业务逻辑层与持久化层之间的交互事件。

一般来说,凡是涉及到层与层之间的交互,事件可采用异步模式,提升伸缩性。
而在层内,则需要细分。轻活(事件处理花费的时间较少),采用同步模式;重活(事件处理花费的时间较多),采用异步模式。

除了上面所说,最近我还在想:
0)将UI、Domain、DB看成一个动态的整体,如何使用四象图去描绘,这可能要舍弃或改造UI、Domain、DB这些概念。
1)事件是刻画行为特征很普遍的一种方法,除了从UI领域借鉴的事件监听器的实现方式,有没有更优雅的方式,比如模式匹配。
2)事件还有一个用法,就是用来实现场景规约(不是场景本身),目前我所想到的比较粗糙的方法,比如“策略模式+中介者模式/责任链模式”等。

1年前的老帖子啊,发现LZ对COAD的四色完全没有掌握精髓,倒是后面提问的这位,深刻的理解了,所以才有这么多问题。
话不多说,举例说明吧。
public void returnBorrowedBook(BorrowedBook borrowedBook) {
Account borrower = AccountFactory.getAccount(borrowedBook.getUsername());
Book book = borrower.returnBorrowedBook(borrowedBook);
Library.putBook(book.getSerialNo());
}
这个是你原例中的还书,我很奇怪的就是你从BorrowedBook得到一个book,居然要绕这么大圈子,这个Role里面不就有一个get方法么?而且Library的方法还是个static的。。。看到这里真的无语了。
四色的最核心的思想是 - loosely decoupling!!!
LZ好好思考下吧,别再说那么多大道理了。。。您看的东西太多,理解得却太浅显。。。

[该贴被ericyang于2012-07-23 16:16修改过]
[该贴被ericyang于2012-07-23 16:17修改过]

2012-07-23 16:13 "@ericyang"的内容
LZ好好思考下吧,别再说那么多大道理了。。。您看的东西太多,理解得却太浅显。。。 ...

嗯,很多细节的实现并不好,话说得也有过了,甚至还存在不少错误。有则改之,无则加勉。但你可能也没有花点时间,了解一下四象图中合理的东西。

高内聚、低耦合只是核心思想?我可不认同,“高内聚、低耦合”不过是衡量软件设计的一个角度而已。在领域建模上,用户心智模型与程序员的心智模型的一致,比这个重要得多。

此外,收件箱你发信说我如此度量,删了你的回帖,我没有这权限的。呵呵。