只供参考,喜欢请支持正版图书
边界在UML图符里的定义只是一个简单的矩形框,矩形框的四个边决定了边界的内外。而在Rational的Rose这一最为著名的建模工具里,干脆连这个元素都省掉了。所以,相对于其他的UML元素,边界可能是最简单的,但也是最容易混淆的。
面向对象里,任何一个对象都有一个边界,外界只能通过这个边界来认识对象,与对象打交道,而对象内部则是一个禁区。我们把边界放大了看,这个世界上任何一种东西我们都不可能知道它本质上是什么,而只能通过它的行为、外观、性质来描述它是一个什么东西。行为也好,外观也罢,这就是这个东西的一个边界,我们就是通过边界来认识事物的。
但是我们不能先有需求再倒过来推定边界,因为在建模过程中需求是晚于系统边界出现的。在收集需求时,我们总要先假定一个范围边界,在这个边界内寻找需求,而找到的需求的集合又决定了最终边界的大小
所以在需求出来之前,我们必须先设想一个边界,这个边界的大小是不确定的,随着需求的明确,边界也逐步变得明朗。但是问题出在确定需求靠什么?靠参与者和用例对吧?而参与者和用例得以明确的前提条件是边界是确定的,而偏偏这个时候边界是无法确定的。是的,这是一个矛盾,实际上需求就是在不断地调整这个矛盾的过程中逐步明确进而更加确定边界的。这个调整过程不可避免地会导致参与者和用例的变化。所以需求过程是一个动态的过程,不可能一蹴而就,也因此统一过程需要迭代,而不能采用瀑布方法。
3.4.1 边界决定视界
而收集需求和开发软件的过程,多少也像是盲人摸象。为了更接近真相,我们能够做的就是不断变换边界,改变视界,从更多的侧面去描述同一个信息,以求最大程度地符合真实的需求(读者在6.1业务建模工作流程一节里会看到同样的用例在不同的视图里展现的情况)。
3.4.2 边界决定抽象层次
抽象层次的重要性在2.3抽象层次一节里已经讲过了。在分析过程中我们总要决定选择一个抽象层次来展开描述。但是选择一个合适的抽象层次并不容易,一辆汽车有几十万个零部件,我们怎么把汽车说清楚呢?我们可以通过设定边界、组织边界大小来决定抽象层次,层层推进地把汽车描述清楚。比方首先设定边界是整辆汽车,这时我们必然站在汽车外面,观察到的是汽车的大小、颜色、质地等这些东西;下一步将边界设定在汽车内部,我们观察到的是方向盘、仪表盘、中控板这些东西;再下一步,可以将边界设定在仪表盘内部,我们观察到的就是指示盘、电路、齿轮这些东西了。
这是一种自顶向下的方式。也就是说,通过逐步缩小边界进而影响到我们可以观察到的事物,也就决定了我们的抽象层次,使得我们的分析粒度可以有条不紊地逐步细化。当然,我们也可以采取自底向上的方式,先把边界设定到较小的范围,比如从发动机开始讲起,扩大到传动系统,再扩大到整车性能。
还是回到软件工作上来。在建模的时候,如果是一个很庞大的系统,信息量之多会超出人脑的处理能力,进而失去分析能力。这就需要很好地把握抽象层次,排除掉非本层次之内的信息,自顶向下地把整个系统描述清楚。边界的设定可以帮上大忙。
比如有一个大系统,其涉及的单位包括商业网站、银行、政府机构、工厂、物流、批发商、零售商等,显然这里面的业务是非常错综复杂的。我们可以把边界设定为整个商业过程,得到的参与者就是商业网站、银行、政府机构、工厂等,进而得到商业网站→宣传商业信息、银行→管理财务、政府机构→监管市场、工厂→生产商品等这些抽象层次非常高的用例。为这些用例建模、获取领域模型、建立业务架构等工作,确保参与者都能达到其业务目标,整个商业模式得以实现。再接下来,把边界缩小到宣传商业信息领域,也就是商业网站部分,进而降低了抽象层次,就会得到广告策划人员、平面设计人员、网站管理员等参与者,进而得到策划广告、设计广告、发布广告等用例,而这些粒度小一些的用例保证满足宣传商业信息这一大用例。如此这般,逐层推进,直到抽象层次降低到对象的级别。
3.4.3 灵活使用边界
其实边界不仅能够在需求方面发挥作用,在设计层面也能发挥重要的作用。软件设计也面临着很大的信息量,既要实现需求,又要保证性能,要具有扩展能力,还要友好易用。如果把这些要求都掺杂在一起,设计师的脑袋就得痛了。这时设定一些边界就能有效地降低复杂度,比如将实现需求的任务交给分析模型,在这个边界内只考虑需求实现;将扩展能力交给框架设计,在这个边界内专心设计灵活的框架;然后再在框架的约束下把分析模型转化成设计模型。这就比在分析模型中考虑扩展能力简单得多了。
业务实体是类(class)的一种版型,特别用于在业务建模阶段建立领域模型。业务实体是业务模型中非常重要的一个因素,它为问题领域中的关键概念建立概念化的理解,是人们认识问题领域的重要手段。如果说参与者和用例描述了我们在这个问题领域中达到什么样的目标,那么业务实体就描述了我们使用什么来达到业务目标以及通过什么来记录这个业务目标。实际上,业务实体抽象出了问题领域内核心和关键的概念,如果把问题领域比喻成一幢大楼的话,业务实体就是构成这幢大楼的砖瓦和石头。
官方文档对业务实体的定义是:业务实体代表业务角色执行业务用例时所处理或使用的“事物”。一个业务实体经常代表某个对多个业务用例或用例实例有价值的事物。一般而言,一个好的业务实体不包含关于其使用主体和使用方法的信息。
首先,业务实体是来自现实世界的,在我们建模的问题领域里一定能够找到与它相对应的事物,并且这个事物是参与者在完成其业务目标的过程中使用到的或创建出来的。例如,饭店中的业务实体有菜单和饮料;而在机场,机票和登机牌是重要的业务实体。有时候,业务实体不一定对应一个具体的事物,它也可以表示一个现实中的概念。比如在有关心理治疗的场景下,患者的情绪也可以用一个业务实体来描述。
其次,业务实体一定是在分析业务流程的过程当中发现的,而业务流程实际上就是业务用例场景。这意味着业务实体必须至少被一个业务用例场景使用或创建,对业务用例场景没有贡献的事物,即使它是客观存在的,也不应当为它建模。例如有一个到商店购买衣服的业务用例,我们在分析购买衣服的过程时,虽然衣服挂在衣架上,但是衣架没有对购买衣服的过程产生贡献,我们就不应当建立衣架这个业务实体。
最后,业务实体作为类的一个版型,具有对象的所有性质,包括属性和方法,同时也具有对象的独立性,即业务实体只应当包含它本身固有的特性,而不能包含外界是如何使用它的信息。这一点应当很好理解,一把刀就是一把刀,对于这个业务实体我们只能描述它的大小、材料、外观、锋利程度等,却不能描述它是用来切菜的。因为它是不是用来切菜,不是取决于它本身,而是取决于特定的场景,比如厨师在厨房里做菜的场景;换一个场景,或许它就变成了劫匪用来抢劫的凶器。
3.5.1 业务实体的属性
属性是用来保存业务实体特征的一个记录,业务实体的属性集合决定了它的唯一性。
通常情况下业务实体的属性可以很容易地从它所对应的现实事物中找到。例如钱币,我们可以很容易找到它的属性:面额、材料、大小、防伪标志等。但是一个事物通常有非常多的属性,在建模的时候,我们是否需要把它所有的属性都列举出来呢?不需要。在特定的场景下,我们只需要关心它与这个场景直接关联的那些属性。例如同样是钱币,在用它进行交易的场景里,我们关心的是面额,至于钱币的大小就无关紧要;而在设计点钞机的场景里,钱币的大小就是要考虑的一个重要属性了。实际上这种只关心业务实体与特定场景直接关联的属性的做法,正是面向对象方法中的抽象视角的体现。抽象视角在2.4视图一节中曾经讲到过,读者可以回头查阅。
很多时候属性并不是一个简单的不可再分的概念,属性本身很可能也是一个复杂的业务对象。例如一个银行账户业务实体,它的属性可能包括定期和活期,而定期本身还有很多属性,比如年限、利率等。这就带来一个问题,定期到底是作为账户的一个属性存在,还是单独将它建模为一个业务实体呢?一般来说,如果只有一个对象可以直接使用这个属性,或者只能通过对象才能访问到这个属性,它就应当作为一个属性存在;否则就应当把它单独建模成一个业务实体。怎么理解?就拿账户和定期来举例,如果读者使用的是招商银行的一卡通,就会知道,一卡通用户手里就只有一个账号,定期没有单独的账号,更没有存折。对定期的所有操作必须通过一卡通账号进行。这种情况下,不论定期有多复杂的属性,它也只应当作为一卡通的账号的属性存在;而在其他银行,办理定期时会单独开立一个账号,甚至会有一张存单,客户可以直接处理这个定期账号。在这种情况下,应当单独将它建模成为一个业务实体。实际上这也是面向对象方法中封装原则的应用,不能因为一个对象内部很复杂,就将其拆分为多个对象展现给外部。对象内部不管结构如何,在存取这个对象的外部看来,它都只应当看上去是一个整体。
3.5.2 业务实体的方法
方法是访问一个业务实体的句柄,它规定了外部可以怎样来使用它。比如一台电视,它的方法就是遥控器,我们可以开、关、调声音、调频道,但是我们不可以试图让它飞起来——因为它没有这样的方法。实际上,这种特性也是面向对象方法中对象封装的概念,回顾1.1.3面向对象方法一节,曾经谈到过对象的这样一个特点:对象是“自私”的,即便在伙伴之间,每个对象也仍然顽固地保护着自己的领地,只允许其他人通过它打开的小小窗口(这称为方法)进行交流,从不会向对方敞开心扉。所以,方法就是外部能够使用这个业务实体的全部信息。
换一个角度说,一个业务实体有很多种可能的使用方法,例如一部手机可以用来打电话、玩游戏、听MP3,也可以用来当电子时钟,你甚至可以在遇到坏人时用它来防卫……在建模的时候我们是否需要把所有可能的方法都定义出来呢?不需要,在特定的场景下,只需要关心那些与这个场景有直接关系的那些方法。例如同样是这部手机,在打电话的场景里,我们只需要关心拨号、接听这些方法;在听MP3场景里,我们只需要关心下载、存储、播放这些方法。与业务实体的属性一样,业务实体的方法也同样是面向对象方法中的抽象视角的体现。
3.5.3 获取业务实体
在业务实体的定义里讲到:业务实体代表业务角色执行业务用例时所处理或使用的“事物”。一个业务实体经常代表某个对多个业务用例或用例实例有价值的事物。实际上这个定义就是我们获取业务实体的方法。
首先我们要建立业务用例场景。业务用例场景是参与者实现其业务目标的过程描述,例如我们描述一个寄信人到邮局寄信的用例场景:寄信人到达邮局,购买信封,将信装入信封,写上地址,称重,计算邮资,购买邮票,贴上邮票,邮寄信件,拿走回执。
然后,从业务用例场景中逐个分析动词后面的名词,它们就是业务实体的备选对象。例如邮局、信、信封、地址、邮资、邮票、信件、回执等。根据对象对业务目标是否有贡献这一筛选条件从备选列表中挑选出符合的对象。例如邮局是一个场所,它是寄信的一个约束,或者说是前置条件,对寄信业务目标来说没有直接的贡献,应当把它从列表中去掉。剩下的就成为初始的业务实体。
最后,分析这些业务实体之间的关系,并决定哪些应当单独建模,哪些应当作为属性。例如,地址和邮票都在信封上,其中地址只有信封能够承载,并且也只能通过信封来阅读地址,所以地址应当作为信封的一个属性。而邮票虽然也在信封上,但是寄信人可以对邮票单独处理,比如在购买时邮票还没有在信封上,所以邮票应当单独建模。再比如,邮资实际上等价于邮票的面额,所以邮资这个对象可以被邮票的面额属性代替,不需要为其建立模型。最后,信封、邮票、回执等共同构成了一份合法的信件。
包是一种容器,如同文件夹一样,它将某些信息分类,形成逻辑单元。使用包的目的是为了整合复杂的信息,某些语义上相关或者某方面具有共同点的信息都可以分包。
包是UML非常常用的一个元素,它最主要的作用就是容纳并为其他元素分类。包可以容纳任何UML元素,例如用例、业务实体、类图等,也包括子包。在Rose中我们可以看到默认的三个顶级包:Use Case View、Logic View和Component View,在其下可以按需要建立无限层次的分包。看起来分包似乎是很随意的。但其实UML对分包还是有着一些指导性原则的,分包的好坏是由包之间的依赖关系(见名词解释1)来评判的,事实上在UML里,包之间的关系定义也只有依赖关系。UML认为好的分包具有高内聚、低耦合的性质。
名词解释1:什么是依赖?如果A事物发生变化,B事物必然变化,我们称B依赖于A;反之则无依赖关系
具体来说,分包有这样一些指导性原则:
■ 如果将元素分为三个包A、B、C,那么被分入同一个包中的那些元素应当是相互联系紧密,甚至不可分割的。同时这些元素又具有某些相同的性质,使得包可以抽象出一些接口来代表包内事物与包外的事物交互,以避免包外的事物频繁地直接访问包内元素。这时我们称A、B、C三个包具有高内聚的性质。
■ 包的最理想的情况是修改A、B、C三个包中任意一个包的元素,其他的任何一个包中的内容都不受到影响。这时我们称A、B、C三个包之间无依赖关系或松耦合关系,它们之间可以保持消息通信。
■ 如果实际情况难以做到完全解除依赖关系,那么至少应当保证包之间的依赖关系不会被传递。例如B依赖于A,C依赖于B,当A修改导致B要做出修改时,C不会受到影响。如果做不到这一点,当一个包发生变动时将会引起大范围的连锁反应
■ 包之间的依赖关系应当是单向的,应当尽量避免双向依赖和循环依赖。如果A依赖于B,而B又依赖于A,我们称这是一种双向依赖关系;如果A依赖于B,B依赖于C,而C又依赖于A,我们称这是一种循环依赖关系。双向依赖和循环依赖都是不好的分包。
名词解释2:什么是依赖传递?如果A=B,B=C,由此可以确定A=C,称之为依赖关系可传递;如果A是B的朋友,B是C的朋友,但不能确定A也是C的朋友。
包最主要的用途就是分类元素。但是UML中对包也可以进行一些版型的定义,让包表达一些特定的含义。接下来,以Rational Rose为例,讲解一些常用的包的版型
■ 领域包(Domain Package)
领域包用于分类业务领域内的业务单元,每个包代表业务的一个领域,领域包视图可用于展示这些业务领域的高层次关系。图3.23展示了使用领域包为商品流通过程建模的结果。
■ 子系统(Subsystem)
子系统再熟悉不过了,它用于分类系统内的逻辑对象并形成子系统。子系统包视图可用于展示系统的高层次逻辑结构关系。图3.24展示了使用子系统包为一个工厂ERP系统建模的结果
■ 组织结构(Organization unit)
组织结构包用于分类业务领域中的组织结构,它可以直接用来表述企业的组织结构。图3.25展示了工厂的组织结构。
■ 层(Layer)
层包用于分类软件中的层次,层可以用于展示软件的架构信息。图3.26展示了我们熟悉的三层架构。
分析类用于获取系统中主要的“职责簇”。它们代表系统的原型类,是系统必须处理的主要抽象概念的“第一个关口”。如果期望获得系统的“高级”概念性简述,则可对分析类本身进行维护。分析类还可产生系统设计的主要抽象——系统的设计类和子系统。
从定义中可以读出两点至关重要的性质:
■ 分析类代表系统中主要的“职责簇”,这意味着分析类是从功能性需求向计算机实现转化过程中的“第一个关口”。
■ 分析类可以产生系统的设计类和子系统,这意味着计算机实现是可以通过某种途径“产生”出来的,而不是拍脑袋拍出来的
分析类是从业务需求向系统设计转化过程中最为主要的元素,它们在高层次抽象出系统实现业务需求的原型,业务需求通过分析类逻辑化,被计算机所理解。分析类是需求实现的第一步,虽然在统一过程中分析类被定义为一种过渡类型,意味着它不是一个强制过程。但是笔者在自己的工作经验中认识到,分析类对于系统分析和设计的重要性远远超出过渡类型所能发挥的作用。
笔者建议在整个软件生产过程中花大力气去维护分析类,这项工作对于整个软件的成功能起到十分重要的作用。在很多项目里,甚至可以花更少的力气去维护设计类。笔者这么做当然有充足的理由,在5.6分析模型一节里笔者会阐述为什么应该这么做
既然分析类有这么大的作用,那就让我们先从分析类的基本性质开始,看看到底是什么让分析类成为在软件过程当中真正的英雄。分析类说起来也很简单,加起来总共也只有三个,分别边界类(boundary)、控制类(control)和实体类(entity),这些分析类都是类(class)的版型。
3.7.1 边界类
在从需求向实现的转换过程中,任何两个有交互的关键对象之间都应当考虑建立边界类。
对现实世界来说,边界类的实例可以是窗口、通信协议、打印机接口、传感器、终端等,在计算机世界里,边界类也可以是一个消息中间件、一个驱动程序、一组对象接口甚至任意的一个类。总之,不论是现实世界还是计算机世界里,当我们打算对A对象和B对象之间的交互进行建模时,边界类都可以充当这一载体。下面来看一些边界类的常用场景。
■ 参与者与用例之间应当建立边界类
用例可以提供给参与者完成业务目标的操作只能通过边界类暴露出来。例如,参与者通过一组网页、一组Windows窗口、一个字符终端或者是一只鼠标来使用用例的功能,上述的东西都可以称为用例的边界类
■ 用例与用例之间如果有交互,应当为其建立边界类。
一个用例如果要访问另一个用例,直接访问用例内部对象是不好的结构,这样将导致紧耦合的发生。而边界类可以隔离这种直接访问,其作用相当于一个门面模式。在最终实现时,用例之间的边界类可以演化为一组API、一组JMS消息或是一组代理类。
■ 如果用例与系统边界之外的非人对象有交互,例如第三方系统,应当为其建立边界类。
这通常是因为异构系统、异构数据、访问权限、安全通道等原因。在具体实现时,边界类可以演化为中介和通信协议,中介的例子如网关、通信中间件、代理服务器、安全认证服务器、WebService、SOA组件等;通信协议的例子如HTTP、FTP、SSL、RMI、SOAP等。
■ 在相关联的业务对象有明显的独立性要求,即它们可能在各自的领域内发展和变化,但又希望互不影响时,也应当为它们建立边界类。
例如生产计划和客户服务计划都来源于销售记录和客户关系记录,但是当销售记录和客户关系记录发生变化时,生产计划和客户服务计划对此产生的回应是不一样的。这时在销售记录和客户关系记录与生产计划及客户服务计划之间加入边界类或许就是一个好主意。在实现时,边界类可以转化为一组接口来为这些对象解耦。
一个好的边界类应该具有以下特点:
■ 边界类应该有助于提高系统的可用性。
■ 边界类应该尽可能地保持在较高的层次(如概念层次)上。
■ 边界类应该合理封装介于系统与主角之间的交互。
■ 如果主角改变他们为系统提供输入的方式,边界类就应该是唯一需要改变的对象。
■ 如果系统改变为主角提供输出的方式,边界类就应该是唯一需要改变的对象。
■ 边界类必须“知道”其他对象类型(例如控制对象和实体对象)的需求,以便它们能够得以实施,并相对于“系统内部元素”保持其可用性和有效性。
3.7.2 控制类
控制类用于对一个或几个用例所特有的控制行为进行建模。控制对象(控制类的实例)通常控制其他对象,因此它们的行为具有协调性质。控制类将用例的特有行为进行封装。
控制类来源于对用例场景中行为的定义,换句话说,控制类来源于对用例场景当中动词的分析和定义,包括限制动词的描述。
例如我们曾经提到过的寄信人到邮局寄信的用例场景,该场景描述为:寄信人到达邮局,购买信封,将信装入信封,写上地址,称重,计算邮资,购买邮票,贴上邮票,邮寄信件,拿走回执。在这个场景中,购买、装入、写上、计算、购买、贴上、邮寄的行为都可以成为控制类的来源。
在提取控制类时,要认真考察用例场景中的行为,如果这些行为在执行步骤、执行要求或者执行结果上具有类似的特征,应当考虑进行适当的抽象,例如合并或者抽取超类。同时,也要考察这些行为是否对要建设的系统产生影响而进行一些取舍。例如上面场景中的装入、写上、贴上等行为是寄信人的人工行为,不会对寄信系统产生影响,因而可以舍去。
在UML的定义中,认为控制类主要起到协调对象的作用,例如从边界类通过控制类访问实体类,或者实体类通过控制类访问另一个实体类。但是UML的定义也认为不必强制使用控制类,例如边界类也可以直接访问实体类。
我们应当养成习惯,在边界类和边界类、边界类和实体类、实体类和实体类之间都默认加入控制类,将相关的处理逻辑放到控制类里去,哪怕该控制类只有一个操作。
在设计阶段,控制类可以被设计为Session Bean、COM+、Server Let、Java类、C++类等设计类。从架构角度上来说,控制类主要位于业务逻辑层。控制类的获取对架构设计中的业务逻辑层有着重要的指导意义。
3.7.3 实体类
实体类是用于对必须存储的信息和相关行为建模的类。实体对象(实体类的实例)用于保存和更新一些现象的有关信息,例如,事件、人员或者一些现实生活中的对象。实体类通常都是永久性的,它们所具有的属性和关系是长期需要的,有时甚至在系统的整个生存期都需要。
实体类源于业务模型中的业务实体。很多时候可以直接把业务实体转化为实体
在设计阶段,实体类可以被设计为Entity Bean、POJO、SDO、XML Bean等设计类甚至是一条SQL语句。从架构角度上来说,实体类主要位于数据持久层。实体类的获取对架构设计中的数据持久层有着重要的指导意义。
3.7.4 分析类的三高
分析类是从业务需求向系统设计转化过程中最为主要的元素,它们在高层次抽象出系统实现业务需求的原型,业务需求通过分析类被逻辑化,成为可以被计算机理解的语义
■ 高于设计实现
高于设计实现意味着,在为需求考虑系统实现的时候,可以不理会复杂的设计要求,比如设计模式的应用、框架规范的要求等,而专心地为从需求到实现搭建一座桥梁
以实体类为例,一个实体类可以被设计成Entity Bean,也可以被设计为POJO,不论是哪一种设计实现,都要遵循相关的规范,实现特定的接口等。这些复杂的要求在为需求考虑系统实现的时候就成为一些杂音,要处理的信息越多,越容易分散注意力。而使用分析实体类的话,就不需要顾忌实现问题,专心解决需求问题
■ 高于语言实现
高于语言实现意味着,在为需求考虑系统实现的时候,可以不理会采用哪一种语言来编写代码,也就可以排除特定语言的语法、程序结构、编程风格和语言限制等杂音,而能专注在需求实现上。
■ 高于实现方式
高于实现方式意味着,在为需求考虑系统实现的时候,可以不考虑采用哪一种具体的实现方式。
如果用对分析类,我们只需要用一个认证控制类代表系统需要这样一个程序逻辑来完成需求即可,至于实现方式则可以先放下不谈。
只供参考,喜欢请支持正版图书