建模心法(2)——迈出建模第一步

原文地址:http://www.cnblogs.com/1-2-3/archive/2008/08/04/model-method-part1.html

原文作者:景春雷

                    一错再错的这故事才精彩
                                      ——朴树 《我爱你再见》

摘要

      即使读了再多的书、跟过再多的项目,到了需要自己创建领域模型的时候,还是感觉不知从哪儿下手。就像即使看过再多的小说,到了自己想写小说的时候,仍会感 觉无从下笔……本文将给出3个实用的建模心法,并通过一个实际项目介绍如何应用USM三视图法迈出建模第一步。

殷六侠即将平生第一次下山做任务



殷六侠来到张三丰的禅房。

殷六侠:“师傅,弟子就要下山去了,特来向师傅告别。”
张三丰:“梨亭啊,虽然你平时读书很用功,也跟着师兄们做了一些项目,但这次毕竟是第一次独力建模,心里有没有谱呀?”
殷六侠:“说实话,弟子现在大脑一片空白。”
张三丰:“……”
殷六侠:“弟子虽然已经把《领域驱动开发心经》、《设计模式真经》、《分析模式真经》读了个滚瓜烂熟,可是到了用的时候,还是有一种无处着力的感觉。”
张三丰:“简直都不知道从哪开始对不对?这是很正常的。这次事出紧急,只能由你一个人立即赶往同仁堂,实在有些难为你了。不过好在现在科技发达了,有什么问题可以随时用QQ与为师联系。时候不早了,你快些动身吧。”
殷六侠:“那徒儿就告退了,师傅保重!”
张三丰:“等一等,你进禅房之前可曾听到为师鼓瑟?”
殷六侠:“师傅是否想说建模就如同鼓瑟,音乐的好听与否并不因为单独的某个音符的高低短长,而是所有音符连续起来的效果?”
张三丰:“好,很好。我的七个徒弟里面,除了你五师哥,就是你悟性最高了。快快下山去吧。”
殷六侠:“徒儿告退。”

第一天的QQ聊天记录

殷六侠:师傅,弟子已经顺利到达同仁堂,这边的领域专家用一上午的时间向我介绍了一下住院部的业务,我整理出了十几个用例:(限于篇幅,这里仅列出3个)
      用例(use case) 由用户的目标联系在一起的一组场景
      场景(scenario) 一系列表述用户和系统之间一次交互的步骤。


名词法为什么行不通

张三丰
:很好,用例做得挺不错的,接下来你打算做什么?
殷六侠:谢谢师傅夸奖,做用例其实并不难,只要把领域专家说的东西稍加整理就行了。弟子接下来就准备根据用例制作领域 模型了。我想用名词法,首先找到用例中的名词,例如“收款员”就是实体,与它相关的动词,例如“提交入院登记”就是实体的一个方法。然后我再根据面向对象 设计的原则和设计模式对类和职责进行调整。
张三丰:很遗憾,我不得不指出你犯了一个初学者常犯的错误——把用例中的名词等同于领域模型里的实体。事实上,用例代表的是系统的外观,用例中的名词和系统中的实体没有任何联系。
殷六侠:可是,领域模型里难道不该有一个叫“收款员”的实体么?
张三丰:没错,领域模型里会有一个叫“收款员”的实体,不过它和用例里面的收款员可不是一回事。用例里面的收款员处于系统外部,是一个活生生的人;而领域模型里的“收款员”实体,确切的说应该叫“收款员基本信息”,这个实体只知道收款员的姓名、性别、年龄和权限等信息。
殷六侠:我注意到您用了“知道”这个词,您是不是想说“收款员基本信息”实体知道得太少而无法承担“提交入院登记”这个职责呢?
张三丰:记得《蜘蛛侠》里面的经典台词吧?“能力越大,责任就越大。”对于类来说,知道的越多,操作就越多。一个类有 2种职责:1)知识性职责,包括属性、关联和无副作用的方法;2)操作性职责,就是指类的含副作用的方法。一个原则就是,要把职责分配给最容易取得它所需 的信息的那个类。
殷六侠:可是,建模的时候会面临着3个问题:“模型里要有哪些类?类之间如何关联?类有哪些职责?”应该首先考虑哪个问题呢?或者说哪个问题比较重要呢?
张三丰:记得有句古话叫“程序=数据结构+算法”吧?那么你说是先有数据结构呢?还是先有算法?
殷六侠:若要为解决某个问题设计一个算法,虽然有可能会先考虑数据结构或算法,但是其实它们两个是相互配合、无法单独工作的吧?也就是说,它们在理论上应该是同时产生、同等重要的吧?
张三丰:没错。而类是把数据结构和算法捏到了一起,所以理论上这三个问题也是被同时解决的。
殷六侠:可是恕徒儿愚笨,要同时思考这三个问题我可实在是办不到。
张三丰:好在实体类还有一个更重要的职责:它要具有延续性和生命周期,并且以identity而不是其它的属性来相互区别。我们要首先按这个职责来构建实体和关联,然后再考虑实体的其它职责,这样就简单多了。
殷六侠:那么设计模式是否对找出实体有所帮助呢?
张三丰:应该说用处不大。因为建模的目的是构建一个领域模型来仿真现实的业务,它的结构恰巧与设计模式里的类结构相同 的情况并不多见。需要注意的是,设计模式关注的主要是如何应用OO的技术手段(接口和多态)来简化设计和增加弹性,它们的关注点是不同的。现实业务里组合 的情况很常见,例如合同包含一些产品,产品由部件组成,但是却不一定需要使用Composite模式那样的类结构,因为可能并不需要Composite模 式所提供的那么强大的一致性和弹性。如果勉强使用Composite模式反而会使模型难以理解,还会由于使用了过窄的接口导致大量的向下转型操作,为 Client代码增加了不必要的复杂性。设计模式确实提供了诱人的一致性和高内聚性,但是你得首先找到领域中的一致性才行。
殷六侠:那我该如何找出实体类呢?实体实体,就是实际存在的物体吧?我注意到每个患者床上都挂着一个床头卡,那么模型里应该有一个床头卡实体吧?还有我可以去收集所有的报表,然后从这些报表里的字段来分析出该有哪些实体。
张三丰:我希望你从一开始就有一个清醒的认识:建模是一项无中生有的、100%的创造性工作,并不存在某种方法或公式 可以让你从用例或实际物体里推导出领域模型。领域模型是“分析”不出来的,它是被“设计”出来的。所以,领域模型里会有一些现实世界里并不存在的实体,当 然也会有与现实世界里的物体同名的实体,但是它只是表现现实物体的某个方面,所以它的职责也就很可能与现实物体不同。至于报表,一般多是取自几个实体中的 数据,还要进行汇总等统计操作,所以比较适合用来验证模型,而不是一开的创建模型的工作。
殷六侠:唉,师傅,您越说我就越糊涂,恐怕弟子是要辜负师傅的重托了……
张三丰:别急,其实建模还是有一些实用技巧的,待为师传你建模心法。呃,今天时候不早了,明天再说吧。拜拜
殷六侠:师傅晚安。

殷六侠回到客栈,周围突然一下子变黑了,屏幕上出现一行小字:“正在存盘……”

第二天的QQ聊天记录

殷六侠:师傅早。
张三丰:早。昨天说到哪了?对,建模心法。先传你建模心法1。

建模心法1 寻找线索实体。

殷六侠:什么叫线索实体?
张三丰:就是生命周期恰好贯穿整个业务流程的那个实体。这个实体就像一条线,将整个业务流程中的其它实体串起来,形成星型的结构。
殷六侠:我明白了,例如一个企业的销售业务就是签订合同、执行合同,那么“合同”就是这样的一个线索实体。
张三丰:没错。
殷六侠:让我想想,对于住院管理来说,这个线索实体的生命周期应该在患者入院时开始,患者出院时结束。“病历本”符合这一条件,不过让其它的实体都关联“病历本”似乎不大自然。由于患者可能多次住院,所以患者这个实体的生命周期显然过长而不适合作为线索实体。
张三丰:很好。
殷六侠:这个线索实体可以定义为“患者的一次住院”。嗯……可以叫“住院履历”,或者干脆叫“住院记录”好了。
张三丰:这个名字还算凑合吧。起一个好名字还是很难的,以后有空多去武当山下的酿名斋坐坐。
殷六侠:是,师傅。有了这个线索实体类,其它的几个相关的实体也很自然地产生了。



张三丰
:看上去挺不错的,不过我要提醒你,领域模型是要能满足所有用例的所有场景的,这个模型里没有包括费用相关的实体呀。
殷六侠:是啊,直觉上费用的处理挺复杂的。
张三丰:你的直觉很正确。现在为师传你建模心法2。

建模心法2 寻找相似场景。

张三丰:如果几个用例中都包含相似的场景,例如“计费”,就可以把这些相似的场景抽取出来成为一个单独的用例,再让其 它用例包含(include)这个被抽取出来的用例,不过这不是必须的。最重要的是你要认识到寻找相似场景的意义。越多的用例包含这个相似场景,就说明这 个场景的业务越复杂,设计不当的可能性越高;将来重构的成本也越大。换句话说就是风险越发的高,所以更需要你加倍仔细、小心地处理。
殷六侠:您是说我们要设计一个实体-关系结构,可以满足所有的相似场景?感觉好难的说。
张三丰:没错,有时会感觉太复杂而无法把握,这时可以试试建模心法3。

建模心法3 使用示例场景(Sample Scenario),寻找一致性。

张三丰:示例场景是一组(最好是连续的)模拟真实业务的场景。例如和费用相关的示例场景为:
场景一:患者入院登记。交预缴金200元。申请获得500元担保金。为医保患者,医保卡内有1000元。
场景二:第一天消费感冒通一盒(50元)、抽血一次(30元)、床位费(100元)
场景三:经申请,担保金额度升为1000元。另,护士重新读取了医保卡,医保卡里的余额是2000元。
场景三:镶金牙(1000元,走现金帐户),担保金+账户余额=100元小于最低预缴金额(200元,系统参数),要求续费
场景四:续交现金预缴金2000元。
场景五:护士刷医保卡缴费。
      最好与领域专家共同制作示例场景。要涵盖所有可能的情况,同时注意不要包含现实业务中不存在的情况以避免不必要的复杂性。如果领域专家指出某种情况是不存 在的,应该进一步追问其原因。原因可能是“目前为止还从未出现过这种情况,虽然理论上是合理的”,“这种情况是违反法律或行规的”,“这么做将伤害公司或 客户的利益”,“为了方便某个部门或某类员工的工作”、“也许这么做会更合算,但是我们领导偏偏就要求要按现在的做法去做”等等。无论是何种原因,都可能 成为未来的变更点。虽然目前的设计不必理会这些不可能情况,但是可以思考一下将来发生变更时如何修改现有设计,以此为契机很可能会发现更为简单且富有弹性 的设计。
      上面的示例场景是不完全的,因为没有涉及退费相关的场景(抱歉限于篇幅没有给出退费相关的用例),就以上面的5个示例场景,该如何设计领域模型呢?
      可以注意到示例场景中存在两类金额:预缴金和担保金。执行某医嘱时可能从预缴金中扣钱,但是也可能从医保卡中扣钱。出于需要为患者打印每日费用明细(抱歉限于篇幅没有给出这个用例)和退费,需要记录每笔费用,这让你想到了什么?
殷六侠:这让我想到可以试试账户+变更记录这个模式(关于常用的分析模式,我打算以后再写几篇来专门介绍),让我仔细想想……

1小时之后

殷六侠:我想到医保卡余额与担保金在概念上很相似,这样就可以一致地处理医保卡账户和现金账户了。



殷六侠:领域模型变成这样:



殷六侠:示例场景确实给了我不少感性认识。
张三丰:随着OOA&D的流行,人们普遍认同一开始建模的时候不应考虑实现细节,而应把注意力集中在对领域的 深刻理解上。领域模型里的对象结构与实际的数据库表结构可以有很大的不同;实体间的关联也不一定非得实现为外键关联……不过请不要把可以忽略实现细节等同 于可以忽略细节领域知识。正所谓“细节就是魔鬼”,醉心于抽象和一致性,而没有把同等甚至更多的精力投入到发现不一致的细微之处上,实在是一种很危险的做 法。所谓“具而不抽则罔,抽而不具则殆”,让思维在抽象和具体之间来回移动才更有效。示例场景还给了你一个“横切”用例的机会,可以让你对业务有一个更加 立体的认识。
殷六侠:制作示例场景还是一个发现遗漏用例和提出尖锐问题的好机会。例如刚刚我就很自然地想到“担保金只是对现金账户 来说的么?还是对现金账户和医保账户同时有效?例如当现金账户和医保卡里面的钱都用光了,而担保金为3000元时,这时计费只能走现金账户还是也可以走医 保账户呢?”

小结 USM三视图法

      运用本文介绍的USM(用例-示例场景-领域模型)三视图法,可令 Modeler 从不同视角观察企业业务,让思维在抽象和具体之间来回移动,互相促进、完善,发现未曾注意的细节,找出概念上的一致性,不断加深对领域的理解。希望USM 三视图法成为虚空中的一块踏脚石,帮助 Modeler 迈出建模第一步。
 


参考文献

Craig Larman,
UML和模式应用。机械工业出版社,2004.
Martin Fowler 著,徐家福 译,UML 精粹(第2版)标准对象建模语言简明指南。清华大学出版社,2002。
Eric Evans, 领域驱动设计(影印版)。人民邮电出版社,2007。
Martin Fowler, 分析模式(影印版)。中国电力出版社,2003.
RicCC,分析模式读书笔记。博客园,2008.

posted on 2012-05-27 21:23  ycty  阅读(233)  评论(0编辑  收藏  举报