版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
面向对象设计
10.1面向对象的系统设计
系统设计是将分析模型变换到系统设计模型。在系统设计的过程中,开发者定义了项目的设计目标,将系统分解为粒度更小的子系统。开发者还要选择构造系统的策略,比如硬件/软件策略、持久性数据管理策略、全局控制流策略、访问控制策略、边界条件处理策略等。系统设计后得到的是一个包括子系统分解和每个策略的清晰描述模型。
系统设计由一些活动组成,每一活动都专注于整个问题中某一个部分的系统分解,这些活动是:
标识系统目标。开发者标识并区分应进行优化的各种系统属性的优先次序。设计初始子系统分解。根据用例和分析模型,开发者将系统分解成一些小部分。求精子系统分解以对应设计目标。系统的构建主要包括系统设计、对象设计和实现三个活动。在系统设计过程中,我们将注意力放在实现系统所必需考虑的过程、数据结构和软硬件构件上面。许多彼此有冲突的准则和约束,在分解系统的时候必须得到折衷。
10.1.1系统设计概述
分析所得到的结果是需求模型,需求模型包括如下内容:一个描述非功能需求和约束的集合,其内容涉及:最大响应时间、最小吞吐量、可靠性、操作系统平台等等;一个用例模型,从参与者的视角描述了系统功能一个对象模型,描述了系统所操纵的多个实体;每一个用例对应一个顺序图,展示了参与用例的对象之间的交互顺序。
分析模型从参与者的视角描述了整个系统,但是,不包含系统内部的结构信息和硬件配置信息。系统设计包括如下内容:
设计目标,描述了开发者应该进行优化的系统性质;软件体系结构,描述了根据子系统任务进行的子系统分解、子系统之间的依赖关系、子系统到硬件的映射和诸如对控制流、访问控制和数据存储的决策;边界用例,描述了系统的配置、起始点、关闭点和异常处理问题。设计目标通过非功能需求获取。当需要在多个设计问题之间做权衡时,设计目标可以指导开发者做出正确的决策。分解子系统构成了系统设计的主要内容。开发者将系统分解为多个容易管理的部分,每个子系统分配给一个团队,由该团队独立实现该子系统。图10.1从面向对象角度描绘了系统设计与软件工程中其他活动之间的关系。
10.1.2系统设计概念
系统设计的主要活动是进行子系统分解,并在此基础上定义子系统/构件之间的接口。1.子系统与类
为降低解域的复杂性,我们将一个系统分解成多个子系统,这些子系统由若干个表示解域的类构成。子系统与一定数量的任务相对应,这些任务由单个程序员或单个开发团队来处理。通过将系统分解成多个相对独立的子系统,可以使多个团队在各个子系统上并发工作,也减少了彼此交流所产生的管理上的开销。对在前面已经描述过的火灾报警与调度系统中,经过分解可以得到:
(1)调度者接口子系统,以实现调度者的用户接口;(2)现场工作人员接口子系统,以实现现场工作人员的用户接口;(3)事件管理子系统负责实现事件的创建、修改和存储;(4)资源管理子系统负责跟踪可用资源(如救火车和救护车);(5)地图管理子系统负责描绘地图和位置;(6)通告子系统用于实现现场工作人员的终端与调度者群体之间的通信。
意外事故响应的信息系统的子系统分解
2.服务与子系统接口
子系统的特征是由该子系统提供给其他子系统的服务来刻画。一个服务是一组有着公共目的相关操作。例如,负责提供通知服务的子系统往往定义了发送通知的操作。供其他子系统调用的某个子系统的操作集合就是子系统接口。子系统接口包括操作名、操作参数、类型及其返回值。系统设计关注于每个子系统提供服务的定义,即枚举出所有操作、这些操作涉及的参数和操作的高层行为。当编写子系统接口的文档时,应该注意尽量减少有关子系统实现的细节信息。
3.分层与划分
某个系统的层次分解将产生层次的有序集合。层是指一组提供相关服务的子系统,它多半是通过使用另一层的服务来实现本层的功能。层一定是有序组织的,每一层仅依赖于它的下一层,而且对其上层情况是一无所知的。层次分解中每一层至少包含一个称为垂直切片子系统的某个系统分解子集。在一个封闭体系结构中,每一层只能访问与其相邻的下一层。在开放体系结构中,每一层还可以访问比与其相邻下一层更低的层次。封闭体系结构的一个例子是开放系统互联参考模型(简写为OSI模型),它是由七层构成。每一层负责执行一个已预先定义好的协议功能。另外,每一层都提供自己的服务,使用比其低的层次服务(图10.5)。
直到最近,OSI模型中也只有底部的四层被很好地标准化了。CORBA和JavaRMI允许我们通过向远程对象发送消息,以透明地访问应用层的构件,这一过程就跟向本地对象发送消息一样,CORBA高效地实现了表示层和会话层(参照图10.6)。
开放体系结构的一个例子是Java的Swing用户接口工具包。一般来说,体系结构的开放允许开发者绕过高层直接访问低层接口以克服性能瓶颈。
封闭分层体系结构具有如下期望的特性:子系统之间具有低耦合的特征、子系统可以被增量地集成和测试。但是,每一层引入了一个速度和存储管理费用问题,这就会导致一些非功能属性难以满足。此外,在随后的新修改版本中向原有系统加入新的功能已被证实有困难,特别是加入一些事先没有预料到的功能。实践当中,一个系统很少被分解超过五层的情况。
处理复杂性的一种方法是划分,将系统分解成对等的子系统,每个子系统负责一类不同的服务。我们利用分层与划分来进行一个子系统分解。首先使用划分方法,将一个系统分成顶层的几个子系统。这样得到的每个子系统,如果还存在比较复杂的子系统,则对它进一步进行分层的处理,将其分解为层次更低的多个小子系统,直到小子系统简单到可以由单个的开发者实现为止。10.1.3系统设计活动:从对象到子系统
系统设计需要考虑在需求分析文档中描述的非功能属性。我们通过车辆路程规划系统来展示系统设计活动。1.出发点:车辆线路规划系统的分析模型
司机采用车辆路程规划系统可以让家里的计算机通过网络上的旅程规划服务来规划自己的旅程。旅程规划在服务器上保存起来,并为以后的检索服务。旅行规划服务必须支持多个司机。车辆路程规划系统中的规划行程用例
用例名称规划行程用例事件流1.司机激活其计算机并登录到旅程规划网络服务系统。2.司机输入对旅程的约束,应是一个含多个目的地的序列。3.基于地图数据库,规划服务计算出按照规定顺序访问的目的地的最短路程。计算结果是一个带有一系列路口和方向列表的行程段序列。4.司机能够通过添加或删除目的地来重新设计旅程。5.司机以名称形式在规划服务的数据库中存储规划好的旅程,以便日后检索。司机发动轿车开始其旅程,此时车载计算机将给出具体方向,这是基于规划服务中的旅程信息和车载的全球定位系统对当前位置的提示完成的(在图10.9中)。
车辆路程规划系统中的执行行程用例
用例名称执行行程用例事件流1.司机启动其轿车,登录到车载的路线助手系统上。2.成功登录后,司机规定使用规划服务系统规划行程。3.车载的路线助手系统从旅程规划服务系统获得目的地、方向、行程段和路口的信息列表。4.给定当前位置,线路助手系统为司机提供下一个方向集合。5.司机到达目的地,关闭路线助手系统。车辆路程规划系统和执行的分析模型
路口路口是指若干地理上行程段的交汇点。目的地目的地表示了司机想去的位置。方向给定路口和邻接的行程段,方向是用自然语言描述的如何驾驶车辆到达指定的行程段。位置位置是由车载全球定位系统或通过计算车轮转过的数目得出的轿车所处的位置。规划服务规划服务是一个网络服务器,能支持旅程,以路口和行程段序列的形式连接多个目的地。线路助手线路助手根据轿车当前位置和下一站路口信息,为司机指出方向。行程段行程段展示了两个路口之间的路程。旅程旅程是两个目的地之间的方向序列。此外,在需求获取中,客户对车辆路程规划系统标识了如下的非功能需求:
(1)系统与计划服务是通过无线调制解调器来连接的。假设无线调制解调器在初始目的地也能正确地工作。(2)
一旦旅程开始,即使调制解调器与计划服务的连接失败,系统也应该给出正确的方向。(3)系统应该尽量减少连接时间以便降低操作开销。(4)只有在与计划服务能够联系的情况下才可以重新规划线路。(5)计划服务要能够至少支持50个不同的司机和1000个旅程。2.明确设计目标
明确系统设计目标的含义,是进行系统设计的第一步。定义明确了系统必须关注的质量属性。许多设计目标可以从非功能需求或应用领域中抽取出来。另一些设计目标则应从用户那里得到。下面我们总结了相关的设计目标。
系统的设计目标
可靠性:系统应该是可靠的[非功能需求2的泛化]。容错性:系统应该在失去与线路服务时能够容错[非功能需求2的另一种说法]。安全性:系统应该是安全的,即不允许其他的司机或是没有授权的用户访问某个司机的旅程规划[由应用领域推导出来]。可修改性:为使用不同的线路服务,系统应该是可以修改的[对由开发人员所做更改的预测]。
我们通常可从一长串期望的属性列表中选择出设计目标。表6-2到表6-6列出了一些可能的设计准则。这些设计准则分成五组:性能、可靠性、成本、维护和最终用户准则。性能、可靠性和最终用户准则往往从需求中明确获取,或者可以从应用领域中获取。成本和维护准则由客户和供应商提出。
性能准则(表10.1)包括了对系统速度和空间的需求。
可靠性准则(表10.2)决定对减少系统崩溃及随后造成的危害所做的努力程度。
成本准则(表10.3)包括开发、配置和管理系统的成本。注意,成本准则不仅包括设计上的考虑,还包括管理上的考虑。当新系统取代旧系统时,我们应该确保向后兼容性,或减少移植到新系统的开销。在各种成本中(如开发成本、最终用户培训成本、移植成本和维护成本),我们必须做出权衡。维护准则(表10.4)决定完成部署后再改变系统的困难程度。这些内容具体涉及:增加新的功能有多困难?对现存功能进行修改有多困难?系统能够适用不同的应用领域吗?如果需要将系统移植到另一个平台上,需要花费多大努力?这些准则很难进行优化和事先规划,因为很少能清晰地给出项目成功的程度和系统可操作的时间周期。最终用户准则(表10.5)包括从用户视点出发所涉及的属性,这些属性与性能准则和可靠性准则并不重复。这些内容具体涉及:软件使用和学习的困难程度?用户是否能够在系统上完成所需任务?在与系统签约的客户并非系统用户时,这些准则通常不会得到关注。
当定义设计目标时,这些准则通常只有一个很小的子集被考虑到了。例如,希望开发一个安全、可靠并且廉价的系统是不现实的。典型情况下,开发者应该优先考虑一些设计目标,将这些目标与其他设计目标进行权衡(参表10.6)。此外,还要权衡管理目标与技术目标。总之,一旦有了清晰的设计目标,我们就可以对子系统进行初始分解了。
表10.6设计目标权衡的例子
权衡基本原理空间与速度如果软件的响应时间或吞吐量不满足需求,则可以使用更多的存储空间(例如,高速缓冲,更大的冗余)来加快软件的执行速度。如果软件太大,则可以牺牲一定的速度对数据进行压缩处理。交付时间与功能如果开发进度滞后于时间表,项目经理可以按时交付功能比已经明确的功能要少,或只能够推迟交付全部功能。契约软件通常更强调功能,而商业外购软件项目则更强调交付日期。交付时间与质量如果测试滞后于时间表,项目经理可以按时交付带有已知错误的软件,或推迟交付带有少量错误的软件。交付时间与人员安置如果开发进度滞后于时间表,项目经理能够向项目中增加资源以提高生产率。在多数情况下,这种选择只适用于早期项目:新的人员要经过培训方可使用,这样增加资源通常会降低生产率。注意,增加资源还会增加软件的开发成本。3.明确子系统
在面向对象分析中我们介绍了一些像Abbotts启发式方法之类的对象鉴别技术,这一思想也同样适用于子系统的鉴别。当新问题被进一步提出时,将对子系统分解进行如下一些修改:将一些子系统归并为一个子系统,将一个复杂的子系统分成更多的部分,在一些子系统中添加新功能。设计者最好通过集体讨论来解决分解问题。初始子系统分解应该从需求的功能模型中导出。例如,在车辆路程规划系统中,我们主要标识了两组对象:一组与规划行程用例相关,另一组与执行行程用例相关。行程类、方向类、路口类、旅程类、规范服务类和目的地类被用于所有的用例上。该类集合由于整体用于行程类,所以设计是紧密耦合的。我们决定将这些类分给规划子系统中的规划服务,其余的类则分到行程安排子系统中(图10.11)。这样跨越子系统边界的关联只有一个。子系统确认规则要求保持功能相关对象的整体性。将对象分组归入子系统的规则是:将在同一用例中明确的对象归入同一个子系统;为对象创建一个专门的子系统,用来在子系统间移动数据;尽量减少跨越子系统边界的关联数目;同一个子系统中的所有对象应该是功能相关的。
子系统分解应该通过减少这些子系统之间的耦合程度,以降低解答域的复杂性。层面设计模式通过简单的、统一的接口来封装子系统,以求达到进一步减少类之间的依赖性。例如,层面仅提供对子系统公共服务的访问权,隐藏其他的所有细节,有效地减少了子系统之间地耦合度。
在初始的子系统分解中,我们往往将功能相关的类加以分组,接着标识出子系统。当这些子系统可以作为层面设计模式候选时,我们用一个类加以封装。
图10.12层面设计模式的一个示例
10.2面向对象目标贯彻
10.2.1面向对象设计的目标贯彻
我们将介绍一些确保设计目标得以贯彻的系统设计活动。描述与子系统设计有关的管理问题,例如编写文档、责任分派和交流。我们将特别关注:对商业外购构件和遗留构件的选择;将子系统映射到硬件;
持久性数据管理基础设施的设计;访问控制策略的规格说明;全局控制流的设计;边界条件的处理。
对上述这些特别关注的内容,现说明如下:商业外购构件或遗留构件可更经济地实现特定子系统。我们应该在初始子系统分解时,仔细考虑这些内容;当系统被部署到一些结点上时,需要关注附加子系统的可靠性及性能问题;管理那些生命周期比系统执行一次的时间还要长的状态,会对整个系统的性能造成影响,这一需求导致需要标识一个或更多的存储子系统;保护共享对象以便用户对这些对象的访问是受控的。访问控制将影响对象在子系统中的分布方式;决定操作顺序,该顺序会影响子系统的接口;一旦所有的子系统被确定下来,开发者就可以决定单个构件的启动和关闭顺序。10.2.2系统设计活动概述
开发者在分解系统时需要把握好一些系统级的问题。特别是要把握好以下几个问题:
l
硬件/软件映射问题;l
数据管理问题;l
访问控制问题;l
控制流问题;l
边界条件问题等。在硬件/软件映射方面,我们要考虑:系统的硬件配置是什么?哪个结点负责哪个功能?结点之间的通信是如何实现的?哪些服务是否是用现存的软件构件实现的?这些构件是如何被封装的?考虑硬件/软件映射问题会导致附加系统的定义。这些附加系统的功能之一,就是负责将数据从一个结点移动到另一个结点,以处理并发和可靠性问题。商业外购构件允许开发者更经济地实现复杂的服务。用户接口包和数据库管理系统是商业外购构件的主要例子。在数据管理方面,我们要考虑:哪些数据应该是持久性数据?持久性数据应该被存储在哪里?如何访问它们?在许多不同的情况下持久性数据都有系统瓶颈:系统中的大部分功能都与创建或操纵持久性数据有关。正是由于这个原因,对数据的访问应该是快速的、可靠的。检索数据的速度问题,以及数据之间的一致性问题等,都必须在系统层上得到一致地通盘考虑。在访问控制方面,我们要考虑:谁能够访问什么数据?访问控制能够动态改变吗?访问控制是如何标识并实现的?访问控制和安全是系统层面上的问题。访问控制的一致性是跨越整个系统的。在控制流方面,我们要考虑:系统的操作序列是怎么样的?系统是由消息驱动的吗?系统每次能够处理多于一个用户的交互吗?控制流的选择对子系统的接口是有影响的。如果选择的是消息驱动控制流,子系统将提供消息句柄。如果选择的是线程,子系统必须保证临界区的互斥。
在边界条件方面,我们要考虑:系统是如何初始化的?系统又是如何关闭的?异常案例是如何被侦测并处理的?系统初始化和关闭往往能够展示出一个系统含有的绝大部分复杂性,分布式环境更是如此。初始化、关闭和异常处理对所有子系统的接口都有影响。图10.13描绘了系统设计的活动。每个活动涉及我们前面描述的问题。下图所示的每个活动,所表示的系统设计是高度迭代的活动,这将导致新的子系统的确定、现存子系统的修改以及对所有子系统产生影响的系统层面上的改动。
图10.13系统设计活动
10.2.3UML部署图
UML部署图用来展示系统运行期间构件和硬件结点之间的关系。构件是自含的实体,可为其他构件或参与者提供服务。在UML部署图中,结点用含有构件图标的盒子来表示。构件之间的依赖用带箭头的虚线来表示。图10.14描绘了一个用两个网络浏览器访问一台网络服务器部署图的例子。图10.14UML部署图展示了构件分配到不同结点的情况和构件之间的依赖关系
图10.14中部署图的关注点,主要集中在将构件分配到不同的结点上,提供每个构件的高层视图。构件能够被求精,包括它们所提供的有关接口信息和它们所包含的类信息。图10.15WebServer提供了两个接口:一个浏览器既可以通过读接口访问通过统一资源定位器(URL)所标识的文件中的内容,又可以通过写接口发送表单。
图10.15WebServer的求精视图
10.2.4系统设计活动中的设计目标贯彻
为了确保子系统分解,我们需要知道所有的非功能需求和解决实现阶段的任何限制条件。首先,我们标识出一些设计目标,接着,我们将通过以下活动对子系统分解进行求精:
(1)将子系统映射到处理器和构件上;(2)标识并存储持久性数据;(3)提供访问控制;(4)设计全局控制流;(5)标识边界条件;(6)评审系统设计。1.将子系统映射到处理器和构件
第一步,选择硬件配置与平台。许多系统运行在不止一台计算机上,而且系统的运行依赖于对企业内部网络和因特网的访问。因此我们需要确保将子系统分配到多台计算机上,以设计其基础设施来支持子系统之间的通信。这些计算机在UML部署图中,用一些结点来进行建模。因为将构件映射到硬件活动上,对系统性能和复杂性有着重要的意义,我们将在系统设计中尽早地进行这一活动。选择一个硬件配置也包括选择虚拟机,要构建的系统将在其上面运行。虚拟机包括操作系统和任意所需的软件构件。然而,对虚拟机的选择将会受到来自客户的约束,还会受到成本因素的限制。在车辆路程规划系统中,我们从需求得知:规划子系统和路由子系统运行在两个不同的结点上。前者运行在基于网络服务器的因特网主机上,后者运行在随车携带的计算机上。图10.16给出了车辆路程规划系统所带有的两个结点的硬件分配,分别称为随车携带计算机和基于Web的网络服务器。图10.16将车辆路程规划系统子系统分配到硬件上。将路由子系统分配到随车携带计算机上;规划子系统分配到基于Web的网络服务器上
第二步,将对象和子系统分配到结点上。在车辆路程规划系统的子系统中,路由子系统和规划子系统共享了行程对象、目的地对象、交叉口对象、路段对象和方向对象。这些类的实例需采用一些通信协议,并通过无线调制解调器来进行彼此间的通信。我们创建一个通信子系统来支持该通信。存储在路由子系统中的信息仅构成了旅途规划的部分内容。旅途规划的另一部分内容则存在于规划子系统中。考虑到这些问题,我们需要路由子系统中的对象为规划子系统中的路段和行程提供代理服务。一个能够代表另一个对象的对象被称为“代理”。因此,我们创建了两个新类,路段代理和行程代理,并将这些对象作为路由子系统的一部分。一旦司机需要重新规划行程路线,这个类将会向通信子系统发出透明请求,以检索规划子系统中与路段有关的信息。最后,通信子系统将一个完整的旅程线路从规划子系统转移到路由子系统上。一般来说,将某一子系统分配到硬件结点上,这意味着我们能够将功能和处理能力分配到最需要这些子系统的地方。但随之而来的问题是,这一决策也引入了子系统中的数据存储、数据转移、数据复制和同步数据有关的一系列问题。
图10.17修改后的车辆路程规划系统设计模型
通信子系统
负责传输规划子系统信息到路由子系统中的对象。连接
连接展示了规划子系统与路由子系统间的活动关联。连接对象需要处理网络服务中异常的用例。消息
消息展示了旅程和与它相关的目的地、行程段、路口和方向等方面的信息,可为传输提供编码。2.标识并存储持久性数据
持久性数据的生命周期要长于系统的一次执行周期。数据在系统中的存储位置和存储方式将会影响到系统分解。在一些情况下,例如,在仓库体系结构风格中,一个子系统能够被完全地划分出来进行数据存储。对特定数据库管理系统的选择也会对全局控制策略和并发管理产生影响。例如,在车辆路程规划系统中,我们决定将当前的行程存储到一个可拆卸的磁盘文件中,以便在司机在到达最终目的地之前,如果汽车发生抛锚等故障时,能够随时对行程进行恢复。在这种情况下,使用文件进行记录的方式是最简单、最有效的解决方案,假定路由子系统在汽车抛锚前,在文件上存储有完整的行程,在系统启动时就会加载该文件。然而,在规划子系统中,行程将被存储在数据库中。这个子系统能够被用于管理多个司机的所有行程和产生行程所需的地图。在这个子系统中采用数据库,允许我们在这些数据上执行复杂的查询。我们添加行程文件存储子系统和映射数据库存储子系统到车辆路程规划系统中,以反映这些决策。
图10.18数据存储问题做出决策后,车辆路程规划系统的子系统分解
行程文件存储子系统
行程文件存储子系统负责将旅程数据存储到车载计算机的文件中。因为这个存储旅程的功能只是在车辆抛锚时使用,所以该子系统只支持整个旅程的快速存储和载入。映射数据库存储子系统
映射数据库存储子系统负责将地图和旅程数据存储到规划子系统的数据库中。该子系统支持多个并发的司机和规划代理。第一步,标识持久性对象。首先,我们要标识出哪些数据是持久性数据。在车辆路程规划系统中,行程及其相关类(如交叉口类、目的类、规划服务类和路段类等)必须存储。注意,并非所有的实体对象均要存储,例如,在汽车移动时不停地在重复计算位置和方向等数据就不必进行存储。然而,持久性对象并不局限于实体对象,也可以是边界对象,如在一个多用户系统中,与用户相关的信息是持久性的,一些边界对象的属性也是持久性的(例如,窗口位置、用户界面喜好、长期运行的控制对象的状态)。通常地,我们可以在系统关闭后,通过检查所有必须保存的类,以标识出持久性对象。第二步,选择存储管理策略。存储管理的决策十分复杂,常常受到如下非功能属性的制约:对象能被迅速地检索出来吗?系统一定要执行复杂的查询来检索这些对象吗?这些对象需要大量的内存和磁盘空间吗?目前有三种选择方案来进行存储管理:1)文件;2)关系数据库;3)面向对象数据库。文件是一种由操作系统提供的存储抽象。应用程序以字符序列形式存储其数据,应用程序可定义用什么方式、在什么时间检索数据。文件抽象相对层次较低但是文件的使用中需要应用程序考虑许多问题。关系数据库提供了比文件层次更高的数据抽象。数据是遵照预先定义好的模式类型进行存储。关系数据库提供了并发管理、访问控制和故障恢复服务。关系数据库已成为一项成熟的技术,但对非结构化数据(例如,图像、自然语言文本),速度就显得太慢了。面向对象数据库提供的服务与关系数据库类似。与关系数据库不同的是,面向对象数据库以对象和关联的方式来存储数据;它提供一个上层的抽象并为开发者提供了继承和抽象数据类型。总之,面向对象数据库可以极大地减少存储子系统的开发时间。但是面向对象数据库的查询速度比关系数据库慢,且更不容易调试。图10.19在文件和数据库之间所做的存储管理的权衡
1.选择文件的规则:1)存在大量的数据(例如,图像);2)存在临时数据(例如,磁心存储文件);3)存在低密度信息(例如,档案文件、历史日志)。2.选择关系数据库或面向对象数据库的规则:1)存在并发访问;2)访问比较合适的细节层;3)对相同数据的多重平台或应用。3.选择关系数据库的规则:1)属性上的复杂查询;2)存在大数据集。4.选择面向对象数据库的规则:1)为检索数据扩展关联的使用;2)中等尺寸的数据集;3)对象之间的不规则关联。3.提供访问控制
在多用户系统中,不同的参与者对不同功能和数据具有不同的访问权限。例如,在车辆路程规划系统中,在同一个数据库中存储地图信息和行程信息会引发安全问题。我们必须确保行程仅被发送给创建这些行程的司机。为此我们使用司机类来对司机建模,将该类与行程类关联起来。规划子系统负责在发送行程之前对司机进行标识。最后,我们需要对路由子系统和规划子系统之间的通信信息是否加密进行决策。表10.7依据决策信息修改设计模型
通信子系统
通信子系统负责传送来自规划子系统和路由子系统的行程。通信子系统将司机与行程关联起来,选择一个密钥对要通信的行程信息进行加密,再将行程传送出去。规划子系统
规划子系统负责创建行程与目的地连接的一个序列。规划子系统还负责对来自路由子系统的重新规划请求做出反应。在处理任何请求之前,规划子系统要鉴别来自路由子系统的司机信息。通过鉴别的司机信息以确定哪个行程能够被发送给对应的路由子系统。
司机
一个司机信息表示需通过鉴别的用户信息。该信息由通信子系统调用来记住与用户关联的密码,由规划子系统将行程关联到用户。我们需要为多用户系统中的每个参与者定义一个共享对象,说明该对象的哪些操作可以供这些参与者访问。例如,一个银行出纳员可以发布贷款,借出一定数量的金额。如果交易超出了预先定义的数目,交易必须由经理批准才能通过。经理可以访问该分支的统计数据,但是不能访问其他分支的统计数据。分析员可以访问跨越公司的所有分支信息,但是不能操作个人的帐户交易信息。我们用访问矩阵来对类的访问控制进行建模。矩阵行表示系统中的参与者。矩阵列表示我们要进行控制访问的类。访问矩阵的一个条目元组(类,参与者)称为访问权,它列出了参与者能够在类实例上执行的操作。对象参与者公司分支信息帐户出纳员postSmallDebit()postSmallCredit()examineBalance()经理examineBranchState()postSmallDebit()postSmallCredit()postLargeDebit()postLargeCredit()examineBalance()examineHistory()分析师examineGlobalStats()examineBranchStats()表10.8一个银行系统的访问矩阵
访问矩阵仅定义了静态访问控制。这就意味着访问权限可以用系统对象的属性来建模。在银行信息系统的例子中,我们考察经纪人参与者,该参与者被分配到证券集合中。由于存在规则,一个经纪人不能访问由其他经纪人管理的证券。在这个例子中,我们需要对系统的访问权限进行动态建模,这种类型的访问称为动态访问控制。例如,图10.20定义了这种访问类型是如何采用代理设计模式实现的。对每一个部长职务,我们都会创建一个部长职务代理来确保检查访问权限。为了访问一个部长职务,经纪人向相应的部长职务代理发出消息。如果发出调用请求的经纪人存在与部长职务代理的正确访问权限,部长职务代理将执行相关检查。如果访问被同意,部长职务代理将为部长职务代理消息。否则,操作失败。图10.20一种采用了动态访问控制代理模式的实例
4.全局控制流设计
控制流是指某个系统中的动作序列。在面向对象系统中,动作序列包括了应该执行哪些操作、以什么顺序执行这些操作。注意的控制流机制有三种:过程驱动控制;事件驱动控制;线程机制。一旦选择了控制流机制,我们就能采用一个或多个控制对象集来实现它。控制对象的作用是记录外部事件,存储其有关状态,给出边界上的操作调用和与外部事件相关联的实体对象操作调用的正确顺序。
5.标识边界条件
我们需要对系统的边界条件进行检查,即决定系统如何启动、如何初始化及如何关闭,此外我们还需要定义在数据发生故障和网络断开等情况下,如何处理失败等。我们将负责处理这些条件的用例称为边界用例。例如,现在假设我们对如何让车辆路程规划系统工作在稳定的状态下,有了一个好主意。但是我们还没有搞清楚车辆路程规划系统是怎么被初始化的,具体而言,我们的问题有地图是如何被加载到规划服务中去的?车辆路程规划系统又是如何被安装到汽车上的?车辆路程规划系统是怎么知道应该连接到哪个规划服务上呢?司机是怎么添加规划服务的呢?在分析过程中,尚未标识出边界用例,这一点是正常的。例如,一些系统管理功能可以从日常用户的需求(如注册、删除用户,管理访问权限)中推知出来;而另一些功能则是从设计决策的结果(如高速缓存容量、数据库服务器的位置和备份服务器的位置)。一般来说,我们是通过配置、启动与关闭、异常处理等检测每个子系统和每个持续性对象,以标识出边界用例的。具体如下:
配置。对每一个持久性对象,我们检查该持久性对象在哪个用例中被创建、销毁(或存档)。对那些不在任何一个一般用例中创建或销毁的对象而言,我们需要增加一个由系统管理员调用的一个用例。由此可以根据配置信息得到边界对象。启动与关闭。对每一个构件而言,我们增加三个用例来启动、关闭和配置构件。注意,单一用例可以管理多个彼此之间存在紧密耦合关系的构件。异常处理。对于每一种类型的构件失败(例如,网络中断),系统的后续动作或如何进行反应是由我们决定的。我们使用异常用例对这些决策进行文档编写,这些异常用例是对在需求获取的过程中标识的一般用例进行扩展而得到的。需注意的是,当要容忍一次失败的后果时,异常条件处理可能会导致系统设计的改变,而非仅仅是添加一个异常用例。一般来说,异常是发生在系统执行过程中的一个事件或错误。导致不同异常的原因主要有三个:
硬件失败。硬件老化和失败。操作环境的改变。环境也影响了系统的工作方式。软件失败。可靠系统的开发是一个困难的话题。往往要对一些功能做出权衡,这样才能够使系统设计变得更简单。在车辆路程规划系统中,所做的假设是,源端、目的端的连接一直是可能的,并且在旅行中的通信问题是会影响旅程重新规划的。例如在图10.21中,我们更改了车辆路程规划系统中包括边界用例在内的分析模型。特别地,我们增加了三个用例:第一,司机管理用例,用来增加、删除和编辑司机信息;第二,地图管理用例,用来增加、删除和更新地图以用来产生旅程信息;第三,服务管理用例,用来执行例行的配置程序,启动和关闭服务器。图10.21管理车辆路程规划系统
图10.22车辆路程规划系统中启动服务用例
用例名称
启动服务
进入条件
1.规划服务器管理员登录到服务器。事件流
2.成功登录后,规划服务器管理员执行启动规划服务命令。3.如果前一次规划服务是正常关闭,服务器将读取合法的司机列表、活动的旅程和地图索引。如果规划服务已经崩溃,它将通知规划服务器管理员,并对地图数据库存储做一致性检查。退出条件
4.规划服务是可用的,并且等待来自路由助理的连接。6.评审系统设计
项目经理和开发者需要组织一个评审过程来替代质量改善活动。除了满足系统设计时确定的设计目标外,我们还需要确保系统设计的正确性、完备性、一致性、可实现性和可读性。具体如下:如果分析模型能够被映射到系统设计模型上,则系统是正确的。以下问题可以确定系统设计是否正确:l
每一个子系统是否能够被追溯到一个用例或一个非功能需求上?l
每一个用例是否能够被映射到一个子系统的集合上?l
每一个设计目标是否能够被追溯到一个非功能需求上?l
每一个非功能需求是否在系统设计模型中被很好的把握了?l
每一个参与者是否拥有其访问策略?l
每一个访问策略是否与非功能安全需求相一致?
如果每个需求和系统设计问题都被考虑到了,则系统模型是完备的。以下问题可以确定系统设计是否完备:
l
边界条件是否已经被处理?l
是否在系统设计中忽视了某个功能,存在由于对用例的考虑不够而需再次标识用例的情况?l
是否所有的用例都被检查过了,并且分配给了相应的控制对象?l
是否把握住了系统设计的所有方面(即硬件分配、持久性存储、访问控制、遗留代码和边界条件)?l
是否所有的系统都有定义?
如果不含有任何的冲突,则系统模型是一致的。以下问题可以确定系统设计是否一致:
l
是否所有冲突的设计目标都划分了优先顺序?l
是否有设计目标违反了非功能需求?l
是否存在多个子系统或类重名?l
子系统之间的对象集合是否以一种一致的方式进行交换?
如果对应的系统能够被实现,则模型是可实现的。以下问题可以确定系统设计是否可实现:
l
系统中是否包含新的技术或构件?是否对这些技术或构件的适合性或鲁棒性做了评估?如何进行评估?l
在子系统分解的上下文中,性能需求与可靠性需求是否已经被评审?l
并发问题(例如,竞争、死锁)是否已被考虑?
如果没有参与系统设计的开发者能够理解模型,则系统模型是可读的。以下问题可以确保系统设计是否可读:
l
子系统名是否可理解?l
具有类似名称的实体(例如,子系统、类)是否表示了类似的概念?l
是否所有的实体在同一细节层被描述?
10.2.5管理系统设计
1.系统设计文档编写
系统设计结论在系统设计文档中撰写。系统设计结论用项目、子系统分解(用UML类图)、硬件/软件映射(用UML部署图)、数据管理、访问控制、控制流机制和边界条件描述设计目标集合等内容来表示。系统设计文档定义了开发团队之间的接口,当体系结构层的决策需重新考虑时,系统设计文档又可以用作参考。系统设计文档的读者包括项目管理人员、系统体系结构师(即参与系统设计的开发者)和设计并实现每个子系统的开发者。系统设计文档大纲的示例
1.
介绍1.1
系统目标1.2
设计目标1.3
定义、首字母的缩写词和缩写1.4
参考文献1.5
概述2.当前软件体系结构3.建议软件体系结构
3.1
概述3.2
子系统分解3.3
硬件/软件映射3.4
持久性数据管理3.5
访问控制与保密性3.6
全局软件控制3.7
边界条件4.
子系统服务术语表系统设计文档的第一部分是一些介绍。介绍的目的是提供一个简要的软件体系结构及设计目标的概述。介绍还提供了对其他文档的参考和追踪的信息(例如,相关的需求分析文档,对现存系统的参考,影响软件体系结构的约束等)。第二部分是当前软件体系结构,描述了被替换系统的软件体系结构。如果不存在以前的系统,这个部分可以用对类似系统的当前体系结构来代替。这一部分内容是为了弄清楚系统体系结构师使用的背景信息、系统体系结构师的假设和新系统将会遇到的一些通常的问题。
第三部分是建议的系统体系结构,对新系统的设计模型编写文档。这一部分可以分为七个子部分:
(1)概述给出软件体系结构的大体情况并简明地描述了每个子系统上的功能分配情况。(2)子系统分解描绘了分解成多个子系统及每个子系统的职责。这是系统设计的主要产品。(3)硬件/软件映射描述了子系统是如何分配到硬件和商业外购构件上去的。硬件/软件映射还列出了多个结点和软件复用所带来的问题。(4)持久性数据管理描述了存储在系统中的持久性数据和它所需要的数据管理支撑基础。这一部分主要包括数据模式的描述、数据库选择和数据库封装的描述。(5)访问控制和安全根据访问矩阵描述了系统的用户模型。这一部分还描述了安全问题,例如标识机制的选择、密码术的使用和密钥的管理等。(6)全局软件控制描述了全局软件控制是如何实现的。特别的是,这一部分应该描述请求是如何被初始化及子系统是如何同步。这一部分应该列举并指出同步和并发问题。(7)边界条件描述了系统启动、关闭及其错误的行为(如果新的用例被系统管理员发现,这些用例应该被包含在需求分析文档里面,而不应该放在这个部分)。第四部分是子系统服务,根据操作描述了每个子系统应提供的服务。当完成了初始的系统分解之后,就应该编写系统设计文档了;也就是说,系统体系结构师不应该等到系统设计决策做出后,再发布文档。一旦做出了设计决策或发现了问题,系统设计文档将在设计过程中更新。2.责任分配
复杂系统的系统设计主要由体系结构团队负责。这是由定义子系统分解的体系结构师和将来实现子系统的开发者组成的功能交叉团队。一旦分析模型趋于稳定,体系结构团队就应该开始工作了,并将持续发挥作用直到后期集成阶段的完成。下面就是系统设计的主要角色:
体系结构设计师是系统设计中的主要角色。体系结构设计师确保设计决策和接口风格的一致。体系结构设计师确保配置管理和测试团队的设计一致,特别是配置管理策略和系统集成策略在形式上一致。体系结构设计师是一个主要的集成角色,使用来自各个子系统团队的信息。体系结构设计师是功能交叉团队的领导者。体系结构联络员是体系结构团队的成员,他们代表各自的子系统团队,负责为自己的团队传递和反馈信息,协调接口的变化。在系统设计中,他们主要关注子系统服务;在实现阶段,他们注意关注应用程序员接口(API)是否一致。其它成员还有文档编辑员、配置经理和评审者等。
3.系统设计交流
系统设计中所遇到交流方面的挑战,应该比在分析中遇到的要小的多,但仍然存在,这反映在:
(1)规模。(2)更改。(3)抽象层。(4)不愿面对的问题。(5)目标和准则的冲突。我们在分析中所讨论的技术,可以用于系统设计,即:
为系统设计标识设计目标,并按优先度排序,以显式表示这些设计目标。对所有关注当前系统的分解版本的人来说,该分解版本应该是可用的。维护最新的术语表。在分析过程中,显式地定义术语可以减少误解。面对设计问题。在提交设计决策之前,如果需要更多的信息,则延迟设计决策是有益的。迭代。在实现阶段进行有选择的迭代能够改善系统设计。
4.系统设计中的迭代
由于连续的迭代和变更,系统设计会不断发生。为了防止混乱,变更应该得到控制,特别是在包含多个参与者的复杂项目中。在系统设计中,我们区分三种类型的迭代:第一,当系统设计中不同活动开始时候的迭代,第二,当创建了评估原型来对特定问题进行评估时候的迭代,这时的迭代会发生子系统接口改变。第三,在后期被发现的错误和疏漏时将会触发子系统接口改变的迭代,这些迭代有时还会触发子系统本身的改变。第一种迭代集最好是在集思广益的会议上解决。第二种迭代的目的是解决困难的问题或倍受关注的问题,例如对特定供应商或对技术进行选择之类的问题。第三种迭代要解决这个过程的后期所发现的一些设计问题。10.3面向对象的对象设计
对象设计包括:
重用,即为了利用已有的解决问题方法,我们标识出商业外购构件和设计模式;服务规格说明,即每一个类接口的精确描述对象模型重构,即改进对象设计模型,以提高可读性和扩展性;对象模型优化,即改进对象设计模型,以满足性能标准。
与系统设计一样,对象设计不涉及算法设计,而只是标识问题解决过程中起着举足轻重作用的模式和构件,我们将要讨论这些构造模块以及与它们相关联的活动。我们的重点集中在重用上,即如何来选择构件和设计模式应用。10.3.1使用模式设计对象
在对象设计过程中,开发者负责填充分析过程中所定义的应用程序对象与系统设计中标识出的软硬件平台之间存在的鸿沟。开发者除了标识出系统中正常的解对象外,还建立了一些自定义的解对象,以用来实现遗漏的功能。通常情况下,一个操作的调用者只会关注操作的非形式化说明、该操作对其它操作的影响,以及该操作的边界情况等。为了准确使用一个操作,开发者需要根据约束条件构造有关类、属性、操作的精确规格说明。同样地,开发者可以调整和重用那些有详细接口说明的商业外购构件。最后,开发者重构和优化对象设计模型,达到可维护性、可扩展性、效率、反应时间、实时性等设计目标。10.3.2对象设计总论
系统设计通过两个方面活动,以减少给定问题和机器实现之间存在的差距。一方面,系统设计会涉及比普通机器抽象层次更高的虚拟机器,这是通过购买活动来实现的,购买的内容涉及中间件、用户接口工具包、应用程序框架和类库之类的商业外购构件。另一方面,系统设计标识出需要使用商业化构件的应用域中的对象。图10.24对象设计填补了需求分析中标识的应用对象和系统设计中所选择的商业外购构件之间的差异
图10.25对象设计的四组活动
图10.25说明了对象设计包括四组活动:(1)重用;(2)接口规格说明;(3)重构;(4)优化。在系统重用设计过程中,需要标识实现每一个子系统的商业外购构件。为了实现基本的数据结构和功能服务,开发者需要选择所需的类库和额外构件。开发者通常选择合适的设计模式来解决一般性问题,使得特定类在系统开发过程中不会发生变化。总的说来,构件和设计模式需要在它们使用之前互相协调好。通过隐藏构件和设计模式周围的自定义对象或是通过使用继承,以实现对抽象与求精的目的。在这些过程中,开发者将面临是买构件还是自己开发系统构件这一选择。在接口规格说明过程,在系统设计中标识子系统的功能都能在类接口中进行详细说明,这包括操作、参数、类型规格说明和异常情况等内容。这个过程中,我们标识了进行数据交换的子系统、传递操作以及对象。服务功能的规格说明会对每个子系统接口产生完整说明。这个子系统的功能说明通常被称为子系统API。重构活动通过系统模型利用软件重用来增加代码,或者是来满足其它的设计目标。重构活动可以看作对特殊模型子集的转换过程。在重构的过程中,我们主要是实现系统模型设计目标中的可维护性、可读性以及可理解性。
优化活动主要是实现系统模型中提出的性能要求。这个过程包括选择更好的算法来提高系统执行的速度;使用更好的存储系统;减少连结中的多样性来提高查询的速度;为了提高效率而增加额外的连结等。
对象设计过程并不是按照顺序方式进行的,这些活动通常是并发进行的。通常先进行接口规格说明和重用活动,可产生一个对象设计模型,使用该模型检查某个特定子系统的用例,即该子系统的功能。一旦该子系统的对象设计模型稳定下来以后,接下来就可以进行重构和优化。不管怎样,对象设计过程是一个迭代过程。
一旦设计模型构造好了,就需要进行验证和评审。通过验证和评审之后,接下来我们就涉及如何将该模型到映射到代码上。这时,我们要完成的主要工作是:
通过优化活动,处理系统模型的性能需求。这包括减少关联,以加快查询速度;添加冗余关联,以提高效率;添加导出属性,以减少对象访问时间。实现设计模型中的关联。在这个活动中,我们将关联映射到源代码结构上。将契约映射到异常。在这个过程中,我们描述了契约被违反时的操作行为。这包括发现事故时,产生异常和在系统更高层要处理的异常。将所设计的类模型映射到某一存储模式。10.3.3重用中的概念:解对象、继承和设计模式
1.应用程序对象和解对象
类图可以对应用域和解域进行建模。应用对象,也称为域对象,描述了系统中的域概念。解对象描述了一组不包含应用域概念的构件,比如说持久数据存储、用户接口对象或是中间件。在对象设计中,我们对应用对象和解对象进行求精和细化,并且标识了额外对象以填充对象设计与解对象之间存在的鸿沟。
2.定义继承和实现继承
在分析过程中,我们通过使用继承概念对对象进行分类,这样我们就可以得到两种类。一种是超类或基类,用来描述一般类的共有行为;另一种是子类或导出类,用来描述特定对象的特有行为。在对象设计中,继承的核心是为了减少冗余并增加扩展性。继承机制对实现者而言是难以把握以的,因为使用了集成的缘故,很多新手写出的代码,与他们不用继承机制相比,要更加混乱和不够健壮。使用超类和从其继承的特定子类可以减少类之间的耦合度,提高了效率。但是,这样做会增加超类和子类在继承层次上的耦合度。在图10.26中,我们给出了UML类模型,总结了四种不同类型的继承。图10.26继承元模型
3.授权当要求重用而使用继承的时候,授权是一种可供选择的办法。A类授权B类是指A类为了完成一个操作需要向B类重发一个消息。使用授权可以把重用类和新类之间的依赖关系描述清楚。
4.Liskov替换准则Liskov准则能够为接口的规格说明提供一个形式化的描述,其思想是:开发者通过增添新子类而不用对代码做任何改变,就可以使用由某个父类提供的方法。5.设计模式中的授权和继承
通常来说,什么时候使用授权,什么时候使用继承,并不能很清楚的判别出来,这需要开发者自己的判断和以往开发经验的积累。继承和授权如果结合起来使用,能够解决很大范围的问题:减少抽象接口的耦合度;把从基类中继承的代码封装起来;减少说明服务类和提供服务机制类之间的耦合度。
10.4接口设计
系统设计的关注点在于标识可分配给单个团队或单个开发者的大粒度构件,而对象设计的关注点在于标识这些大粒度构件中的各个对象及对象之间的接口。在系统底层细节迅速扩展的情况下,接口规格说明的关注点在于开发者是否依然能够清楚和准确地进行交流。对象设计中接口描述活动包括:确定遗漏的属性和操作;描述类型签名和可见性;描述不变式;描述前置条件和后置条件。
我们将讨论与接口规格说明有关的对象设计活动,具体内容如下:提供了一个接口规格说明的综述;定义了主要的接口规格说明概念,包括类型签名、约束、协议,介绍了UML的对象约束语言;讨论了接口规格说明的管理问题等。
10.4.1接口规格说明综述
在系统开发的这一点上,我们做出了很多决策并产生很多的模型.但是所有的这些模型,只是反映了系统的部分视点。有很多问题被我们遗漏了,这些问题需要进一步求精。接口规格说明的目标是足够清晰地描述每一个对象的接口,这样就能通过单独的开发者实现这些对象,以满足最小集成的需要。为了达到这个目标,接口规格说明包括以下活动:标识遗漏的属性和操作。标识可见性和签名。定义契约。10.4.2接口规格说明概念
1.实现者、类扩展者和类使用者
我们需要根据开发者的观点,区分不同的开发者:
(1)类实现者负责对有待实现的类进行实现。(2)类使用者在其它类的实现过程中,调用实现类所提供的操作。(3)类扩展者对实现类进行了特定扩展。图10.27类实现者、类扩展者以及类使用者的角色
2.类型、签名和可见性
在对象设计中,我们通过给出完整的类型和可见性信息,对分析和系统设计模型进行求精。属性的类型说明了这个属性可以取值的范围,以及该属性能够进行的操作。操作参数和返回值也需要确定类型。类型约束了参数或返回值可取值范围。一个给定操作、符合要求的参数和返回值类型的元组,称为操作的签名。比如说:系列赛对象的接收选手操作acceptPlayer()需要一个Player类型的参数,并且该操作没有返回值。那么操作acceptPlayer()的签名就是acceptPlayer(Player):void。系列赛对象中的getMaxNumPlayers()操作没有参数,并返回一个整型值,那么getMaxNumPlayers()操作的签名就是getMaxNumPlayers(void):int。
类实现者、类使用者和类扩展者都需要访问类的操作和属性。但是三者的访问范围各有不同。例如,一个类实现者能够访问类的内部数据结构,而这些信息对类使用者来说通常是不可见的。类扩展者仅仅是有选择地访问超类中的一些内部结构。
3.契约:不变式、前置条件和后置条件
契约就是在一个类上定义的、确保有关该类的类实现者、使用者、扩展者都要遵守的假设条件。一个契约说明了类使用者在使用该类之前必须遵守的约束,这一约束也是类实现者和类扩展者在实现和使用时必须遵守的约束。
契约包括三种类型的约束,具体说明如下:(1)不变式。不变式是一个对该类的所有实例都为真的谓词。不变式是和类或接口有关的约束。不变式通常用来说明类属性之间的一致性约束。(2)前置条件。前置条件是在调用一个操作之前必须为真的谓词。前置条件和某个特定操作有关。前置条件用来说明类使用者在调用一个操作之前必须满足的约束。(3)后置条件。后置条件是在调用一个操作之后必须为真的谓词。后置条件和某个特定操作有关。后置条件用来说明类实现者和类扩展者在调用一个操作之后必须满足的约束。在系列赛类中有一个增加选手的方法acceptPlayer(),有一个取消选手的方法removePlayer()和一个取得参加选手的最大数目的方法getMaxPlayers()。
系列赛类中存在这样一个不变式:即确保系列赛类中选手的最大数目应该为正数。在构造一个系列赛对象t的时候,如果所设定的最大选手数目为0的话,那么使用acceptPlayer()方法将违反该操作的契约,因为任意增加一个选手的话,都会破坏最大选手数目为0的条件,因此应该设定最大选手数目为正数。我们可以将这个不变式表述为t.getMaxPlayers()>0。
acceptPlayer()方法适用的前置条件的例子是:如果一名选手希望参加系列赛,那么该选手在申请之前,应确保没有加入到该系列赛中,并且在该选手加入系列赛时,该系列赛所能容纳选手的最大上限没有达到。我们使用一个布尔表达式描述该不变式,其中t代表一个系列赛,p代表一个选手:!t.isPlayerAccepted(p)andt.getNumPlayers()<t.getMaxNumPlayer()。
acceptPlayer()方法适用的后置条件的例子是:在使用accpetPlayer()方法之后,当前选手Players的数目必须比调用该函数之前的选手数目多一个。我们把这个后置条件表述为:t.getNumPlayers_afterAccept=t.getNumPlayers_beforeAccept+1。在此,getNumPlayers_afterAccept表示在调用acceptPlayer()方法之后的选手数目,getNumPlayers_beforeAccept表示在调用acceptPlayer()方法之前的选手数目。通过使用不变式、前置条件、后置条件,我们清楚地说明规格说明的边界条件以及所期望的情况。
4.对象约束语言
我们可以通过对象约束语言(ObjectConstraintLanguage,OCL)的形式来描述一个约束。OCL是一种允许在单个模型元素(例如,属性、操作、类)和模型元组上(例如,关联和参与类)对约束进行形式化说明的语言。一个约束可描述为返回值为真或为假的布尔表达式。一个约束可描述成通过依赖关系关联到一个受约束的UML元素的说明。下图使用UML和OCL描述了在以前章节中提到的系列赛例子的一个类图。图10.28OCL中不变式、前置条件和后置条件的例子
我们选择使用文本形式描述OCL表达式。例如系列赛中的最大选手数目属性为正数的不变式描述如下:contextTournamentinv:self.getMaxNumPlayers()>0。
context关键字表明该表达式所适用的实体。context关键字后面可以是关键字inv、pre和post中的一个,这与UML的<<invariants>>,<<precondition>>,<<postcondition>>是分别对应的。然后就是实际的OCL表达式。但是,由于OCL不是一个过程型语言,因此不能用来表示控制流。对于不变式而言,表达式中的上下文表明了与该不变式相关联的类。关键字self(例如,self.numElements)表示该类的所有实例。我们可以使用点号访问属性和操作。例如,使用self.maxNumPlayers访问当前上下文中的maxNumPlayers属性。在没有歧义的情况下,self关键字也可以省略。对于前置条件和后置条件,OCL表达式的上下文就是一个操作。传递给操作的参数,可以用做表达式中的变量。举例来说,考虑到下述作用于系列赛中acceptPlayer()操作的前置条件:contextTournament::acceptPlayer(p)pre:!isPlayerAccept(p)。
在约束!isPlayerAccept(p)中用到的变量p,表示传递给acceptPlayer()的操作参数为p。由于该约束是一个前置表达式,因此这个约束在acceptPlayer()操作执行之前必须为真。这个约束可以用自然语言可以表达为“acceptPlayer(p)假设p以前没有参与过系列赛”。我们可以为同一个操作写很多前置条件。如果一个给定操作的前置条件不止一个,那么该操作所有的前置条件在其被调用之前都必须是为真的。例如,我们可以描述系列赛在调用acceptPlayer()之前,选手数目没有达到最大数目:contextTournament::acceptPlayer(p)pre:getNumPlayers()<getMaxNumPlayers()后置条件的写法和前置条件是一样的,除了所使用的关键字是post这一点不同之外,后置条件表示要描述的这个约束是在操作返回后为真。例如,下述作用于acceptPlayer(P)的后置条件说明选手p在acceptPlayer()调用之后对于系列赛而言是可知的:contextTournament::acceptPlayer(p)post:isPlayerAccpted(p)。对后置条件,我们通常需要知道在某个属性的一个操作执行前后的值。为了达到这个目的,后缀@pre表示self是某个属性在某个操作执行之前的值。例如,如果我们想说明在调用acceptPlayer()之后,系列赛中的选手值增加了一个,我们需要知道在调用acceptPlayer()操作前后的getNumPlayers()方法的返回值。我们可以写如下后置条件:contextTournament::acceptPlayer(p)post:getNumPlayers()=@pre.getNumPlayers()+1@pre.getNumPlayers()表示在调用acceptPlayer()之前由getNumPlayers()得到的值,getNumPlayers()表示的是,在调用了acceptPlayer()操作之后,由该操作的返回值。和前置条件一样,如果这里对一个操作有多于一个的后置条件,在该操作执行完毕之后,所有的后置条件都必须满足。因此,使用同样的方法,我们可以将removePlayer()操作的契约写出来:contextTournament::removePlayer(p)pre:isPlayerAccepted(p)contextTournament::removePlayer(p)post:!isPlayerAccepted(p)contextTournament::removePlayer(p)post:getNumPlayers()=@pre.getNumPlayers()-1。
10.4.3接口规格说明活动
1.标识遗漏的属性和操作
我们检查子系统提供的服务描述,并且标识遗漏的属性和操作。2.说明类型、签名和可见性说明类型可以从两个方面对对象设计模型进行求精。首先,我们通过说明每一个属性的取值范围,以增加模型细节。其次,我们把对象模型的类和属性映射到由开发环境提供的类型上。最后,我们标识每一个操作和属性的可见性。3.说明前置条件和后置条件
我们为每个类的所有公有操作定义了契约。契约就是存在于类使用者和类实现者之间的一个协议。一个操作的前置条件描述了类使用者必须遵守的那部分契约,后置条件描述了类使用者在遵守相关部分契约之后,类实现者应该保证相关操作能够正确执行,且在执行后契约得到满足。
4.说明不变式不变式的撰写比前置条件和后置条件的撰写要困难很多。标识不变式的活动与在分析中找抽象类的过程相似。仅有很少不变式是很明显的,而且
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026年清洁小家电行业研究报告:扫地机、吸尘器产品生命周期研究
- 2026年物流行业报告及供应链优化研究
- 2026年家电行业事件营销效果评估报告
- 2026年房地产行业成本压力解析与盈利模式创新报告
- 2026年动漫产业报告:行业现状与市场前景分析
- 2026年汽车自动驾驶技术发展报告及市场应用前景
- 2026年电子信息原材料库存预警及应对策略报告
- 2026年5G通信技术创新报告:产业应用与未来展望报告
- 2026年汽车后市场行业博览会参会技巧与客户获取
- 2026年互联网行业发展趋势报告及政策影响分析
- 实验室安全培训案例课件
- 快消品特殊渠道开发课件
- 2025-2026学年北京市文汇中学教育集团九年级(上)期中语文试卷
- 上海市郊野乡村风貌规划设计和建设导则二
- 风机降噪方案
- 产品开发TR5评审要素表
- 多学科交叉融合驱动下机器人导论课程实践与思考
- 航天科工集团在线测评题
- GB/T 44211-2024消费品质量分级导则家具
- 求职OMG-大学生就业指导与技能开发智慧树知到期末考试答案章节答案2024年中国海洋大学
- 第10章-幼儿园组织文化建设课件
评论
0/150
提交评论