Allen Holub ([email protected])
首席技术官,NetReliance
2001 年 4 月
有关如何构建用例文档的几条初步注释已准备好。考虑将如何使用模板,我已经排列了模板的各节。不过创建模板的次序很少会与使用模板的次序相同。我已经按模板次序介绍了用例,但当我真正把东西拼装在一起时,我不会固守一种形式。我从名称和描述开始,然后进入并着手进行少许方案工作,接着进入并添加实现的注解,再回过头并进行一定的方案工作。然后,我填写期望的结果部分,添加后置条件,就这样一轮一轮的进行下去。
此外,在现实世界情况中,很少有一个人可以独自开发整个用例。理想的情况是,与商业相关的组件(用例名称、描述、方案、用户目标可能还有期望的结果)将主要由市场营销方开发,而工程方起咨询角色。技术项(依赖性、前置条件和后置条件、正式工作流分析、实现需求和注解)将主要来自工程方,而市场营销组织起咨询角色。商务规则与需求通常共同创建。如果现实的客户 — 产品的实际用户 — 也参与到过程中来就更好了。
您将要并行地设计几个构件。在开发用例时发现问题的迄今未知方面是很自然的,在遗忘之前应该回头把它们添加到问题陈述中。将包含各阶段设计的各种文档保存在一起对于大型设计而言是真正的问题,但它很关键。一旦发现思考问题时的缺陷,就必须立即更改 所有相应的文档。通常在我工作的同时,我会将问题陈述、所有相关用例描述、UML 图和代码都放在我面前。当发现采取的方法不对时,能够回滚到以前的设计是很重要的,可是我不知道有任何自动化文档管理软件包能够做到真正“无痛苦”的回滚,要知道设计是由很多类型的文档 — 图画、格式化文本、ASCII 源代码等等 — 组成,而它们可能都会受到更改的影响。(如果谁知道能胜任此任务的文档管理系统,请给我发电子邮件。)
现在我们来看第一个用例。
样本用例
这在上下文中是第一次出现认可(authorization)这一概念,并且它是编写用例如何能找出问题陈述中的缺陷的一个实例。在我第一次处理这个用例时,我写道“家长和这个用例没有关系”但我后来的想法是“可是怎样防止小孩存入任意数目的钱呢?” 认可的概念解决了这个问题,但也引入了几个有趣的方案,我将在下面讨论。
原始的问题陈述的确涉及到安全性问题,但用的方式并不奏效。这里是原始问题陈述的相应部分:
原始问题陈述
有几个安全性问题。象真正的银行一样,小孩们不能通过自己修改存折进行存款或取款;他们必须要求银行做这件事。银行处理这个交易然后更新存折。也就是说,小孩把钱交给银行,银行再把钱放入帐户。银行从帐户取出钱然后交给小孩。(实际上,这意味着只有家长能够存款或取款,但家长应该按要求处理取款请求,否则小孩会以为银行是一个不让他们拿到自己钱的骗子。)
回顾一下,银行从帐户取出钱并交付那种事行不通,因为银行根本不做那件事(出纳员做,银行主管人做,银行不做)。因此,我现在返回并修改问题陈述,使家长对某种交易的认可的概念与在此用例中发现的更一致。
这里是修正的部分:
修正的问题陈述有几个安全性问题。只有银行主管人(即家长)能够更改利率,设立新帐户(和初始余额)或者关闭帐户。尽管对于绝大部分交易,小孩都能完全独立地使用银行,有些交易(存款和贷款)需要家长/银行主管人的批准才能完成。对于小孩而言这样的交易是可见的并且在存折上显示出来,但未经批准的存款不能用于取款。取款不需要家长批准。取款要求会导致一张支票被打印出来。这些支票可以象现实世界支票一样使用。如果支票遗失,则发出停止支付的命令(需要收费)。支票可以交给银行(家长)来换取现金。
我是在写作时想到支票的问题 — 设计过程如何运作的另一个示例。当从不同角度考虑问题时,会发现遗漏了问题的某些部分。
当然,在对问题陈述作了更改后,现在需要得到领域专家批准才能检入新的版本。(既然我自己就是领域专家,那就简单了。)
期望的结果 帐户余额变得更大 用户目标 小孩:尽可能地使帐户余额变大并且注视财富累积。请注意系统的不同用户有不同的目标,而且这些目标经常是相互排斥的。小孩希望帐户余额任意地大,而家长则想确保只有真正的资金 — 小孩们真正可支配的资金 — 被存入。这种目标冲突并不少见,所以当前练习的要点之一就是识别(并解决)这些冲突。
参与者/角色
我将使用上个月讨论的 CRC-card 格式来标识角色。同样,这个列表是我在进行方案工作时逐渐开发出来的;您在这里所见的是该列表的最终形式。
请注意有些参与者是自动化的(出纳员)而其他则是真人。还要注意有些参与者是系统里的被动对象(存折)。这就是用于程序开发的用例与用于 UI 开发的用例可能不同的地方。UI 样式的用例通常只会关心系统的实际用户所扮演的角色。而在 OO 系统里,即使被认为是被动的对象也有定义于其上的职责和操作。(例如,存折可能打印自身内容。)
类 职责 合作者 Kid(银行客户) 填写存款单。 持有存折。 Teller(处理小孩的请求) Parent(银行主管人) 认可存款 Teller(请求存款) Teller 收取存款单。 从银行主管人处获得许可进行存款。 更新存折。 Parent(授权交易),请记住,我这里介绍的组织更多地是为了方便用例的使用者而不是用例的创建者。
令人惊讶的是,我想不出 Account 类的对象能够在此系统中做什么。当前余额由存折保存,并且存折还保存着该帐户交易的记录。于是我怀疑帐户和存折是一回事;Account 所做的就是将一些标识信息分配给交易记录。但如果小孩/客户持有他们的存折,则无需又在帐户中存储标识信息;只需从小孩那里取得存折然后找出他们拥有的帐户即可。
这个问题实际强调了数据库样式的实体关系(ER)世界观和面向对象(OO)世界观之间的不同。将一种世界观映射为另一种世界观称为 对象—关系映射,而这种映射很少是直接了当的,因为需要协调两种冲突的组织性策略。一个好的对象模型很少会生成一个好的数据库模式(反之亦然)。在当前示例中,一个 ER 世界观可能会将 Customer、Account 和 Passbook 都看作有效实体。Customer 包含客户特定的标识信息并且标识客户拥有的各种帐户,Passbook 包含交易历史,Account 则包含帐户特定信息,如当前余额。由于我刚才讨论过的原因,对象模型在那种方式下则不奏效。既然没有事情需要 Account 去做,那么它就不是合法的类。
在 OO 系统中,类如果没有事情可做就不应该存在;必须有职责(操作)。事实上,类完全根据它所能做的事情来定义 — 它用来执行那些任务的数据是应该对外部世界隐藏的不相关的实现细节。帐户可能封装数据,但我想不出任何它将执行的操作,因此 Account 就不是一个合适的类。(有关以这种方式考虑 OO 系统的更多信息,请参阅参考资料。)
另一方面,对象建模者与实体建模者的世界观有从根本上的不同。对象建模者几乎只对操作有兴趣 — 对象做什么。对象实现这些操作所必须保存的状态数据在设计过程的早期通常是不相关的。对象建模者关心静态属性仅当它可以用来区分一类对象与另一类对象。(例如,雇员有薪水,而普通人没有,所以薪水属性可以当作 Employee 的区分特征使用。)但是,数据库实体建模者却只关注静态属性 — 操作对他们而言是非实质的东西。其净结果是那种对于实体建模者是被诅咒者的组织(没有任何状态数据的类、类之间一对一和多对多的关系等等。)在 OO 系统中到处出现。
最终的文化冲突问题围绕着静态数据的组织。例如,在 OO 系统中,委员会的成员将会有效地包含在 Committee 类中。即,成员将是 Committee 类的静态属性。在关系型系统中,更常见的是 Committee 标识作为成员的属性(以便可以在委员会名称上连接或选择)。
但我的做法与前面的原则是相违背的。尽管前面已经说过,我还是将“Account”暂时保留在列表中以备帐户上的某些操作出现。但是我怀疑 Account 最终会消失。
现在已经完成了半个模板 — 在某种程度上,这是不太有意义的一半,因为该模板的这些部分对于组织我的思想更有用,而不是实际上详细说明用例的进展情况。下次我将提供用例定义的更有意义的那些部分:方案及活动图。随着用例进展,这些部分实际上试图制定工作流。
参考资料
本文继续了我编写的关于 OO 设计过程的系列文章。前六个部分是: 入门 开始设计软件 细化问题定义 验证分析 用例,简介 用例规划 工作流程图(连同 UML 的剩下部分一起)在 Martin Fowler 和 Kendall Scott 合著的 UML Distilled, 2nd Ed 一书中有很好的描述。本文地址:http://com.8s8s.com/it/it2026.htm