《(7)面向对象系统设计》由会员分享,可在线阅读,更多相关《(7)面向对象系统设计(53页珍藏版)》请在金锄头文库上搜索。
1、第七讲 面向对象设计,主要内容,一、面向对象设计概述 二、系统体系结构设计 三、软件类的设计 四、面向对象设计原则,一、面向对象设计概述,面向对象设计模型在面向对象分析模型和面向对象程序之间架起了一座桥梁; 面向对象程序的基本概念是程序由一系列协同完成某一任务的一组程序对象组成,每个程序对象有程序逻辑和一些必要的属性,这些逻辑和属性封装在一个单元中;对象之间通过互相传递消息来协调工作,它们共同工作来完成主程序的功能。,从分析到设计 分析的目标是做正确的事,理解问题域的重点目标、对象和相关规则;设计工作是正确地做事,从技术的角度灵活地设计解决方案; 面向对象的分析和设计都基于相同的模型,一般认为
2、没有严格的阶段性和很明显的界限。,面向对象设计的目标 设计软件体系结构,定义系统的高层划分,确定主要组件及其接口; 详细设计软件类或接口,按照架构模式定义信息系统的边界类、控制类和实体类; 设计数据库接口,解决面向对象模型到数据库模型的过渡。,二、系统体系结构设计,体系结构设计概述 层的设计 包的设计,体系结构设计概述 系统体系结构也称为系统架构,是指一个或一组结构,它包含组成系统的软件元素、这些元素对外可见的性质以及它们之间的关系; 系统架构是最高层次的系统分解,包括逻辑设计和物理部署两部分;逻辑架构通过系统的层、包、主要框架、类、接口和子系统的组织方式来描述,物理部署描述了进程分配和网络配
3、置。,层的设计 在分解复杂的信息系统时,用的最多的技术之一就是分层; 在采用分层架构时,下层组件负责对上层组件提供服务,上层组件可以使用下层组件定义的服务,但下层组件对上层组件一无所知; 层与层之间通常是不透明的,每一层都具有独立的职责。,三层架构 在信息系统领域,最常用的分层模型是三层架构模型:表现层、业务逻辑层、数据存储层; 表现层:处理用户和信息系统之间的交互; 业务逻辑层:信息系统所有和问题领域相关的工作; 数据存储层:与数据交互,存储持久数据。,对每个信息系统应用,以上三层是基本的层次结构,具体如何分离取决于问题的复杂程度;复杂一些的系统可以将三层行为分别封装在不同的包中; 伴随着职
4、责的分离,必须遵守一条关于层间依赖的普遍原则:业务逻辑层和数据存储层绝对不要依赖于表现层; 多层架构:当业务逻辑变得越来越复杂时,可以根据系统的实际情况或设计需求,进行进一步的分解,基本的三层结构可以扩展为四层、五层或更多。,包的设计 包是一种逻辑分组的概念,可以取UML模型中的任何一种事物,将相关成分聚在一起,以构成更高层的组织单元; 最常用的方法是将类以包为单位进行分组,例如可以将每一层的所有类组成一个包; 一个包可以包含其他的包。,分包的原则 共同封闭原则:一个包中的各个类应该是由于相似的原则而改变,即将一组职责相似、但以不同方式实现的类归为一个包中,例如将所有处理交互界面的类放在界面包
5、中,将所有处理业务逻辑的类放在业务逻辑包中; 共同复用原则:一个包中的各个类应该一起被复用,即包中包含了一组不同类型的类,它们之间通过相互协作实现一个意义更大的责任,对包中一个类的修改会影响到包中其他类,所以复用其中一个类可能需要同时考虑同一个包中的其他协作类。,在UML中,把包画为文件夹的形式:,设计包图 包图用来描述包及其依赖关系; 包图的目标是用于标识一个完整系统的主要部分; 在包图中只使用两个符号:一个包的标识符,一个虚线箭头,虚线箭头表示依赖关系,箭头的尾部表示被依赖的包,头部是独立的包。,包依赖关系可以是直接的,也可以间接的,依赖关系可以传递; 通过包图,开发人员能够估算某个包中类
6、的复杂度,估算出重用一个包的难易程度。,三、软件类的设计,基本概念 设计属性 设计方法 设计关系,基本概念 软件类是设计阶段中讨论的类,也称为设计类,对应的分析阶段的类称为概念类或领域类; 分析模型中的类通常是现实事物或概念的抽象,这些类要转换为软件世界中的类; 另外,为了完成用例所描述的功能,还必须添加一些计算机领域的软件对象的类。,边界类 边界类的职责是完成系统与其参与者之间的交互。这种交互通常包括接收来自用户和外部系统的信息与请求,以及将信息与请求提交给用户和外部系统; 通常将用户界面或通信接口的变化封装在一个或多个边界类中; 边界类的主要职责是接收用户的触发事件,并通过对界面中的各种图
7、形元素进行编程来进行响应。,建立用户界面原型 设计阶段的任务是使所有的技术明朗化,其中之一就是尽早地建立用例的用户界面原型,即边界类的设计; 通过界面原型可以进一步收集和明确需求,并为构件系统正式的动态模型做铺垫,为设计业务逻辑类的职责和方法提供帮助; 界面原型不需要描述太多细节,通常包含以下内容: 需要要用户输入到系统中的数据窗口或表格; 需要由系统执行的操作按钮; 系统应及时做出回应的事件; 需要由系统输出给用户的数据窗口或消息。,实体类 实体类来源于领域模型中的类,多数情况下对应于领域模型中的领域类;,控制类 控制类代表协调、排序、事物处理以及对其他对象的控制,经常用于封装与某个具体用例
8、有关的控制流; 控制类还可以用于表示复杂的派生与演算,但它们与系统需要存储的任何具体持久信息没有关系; 根据分层原则,控制类并不封装与参与者交互有关的问题,也不封装与系统处理的长效、持久信息有关的问题,这些问题分别是由边界类和实体类封装的,而控制类只对控制、协调、排序、事务处理以及某些复杂业务逻辑进行封装 。,设计类的属性 设计属性的类型和初值:属性的类型和默认的初始值应该在设计模型中表示出来; 设计属性的可见性:属性的可见性指定了该属性可以被其他类利用的程度, UML定义了以下几种可见性: (1)公有的(public)定义为“公有的”属性,在其他任何类中都能访问到,使用“+”号做前缀来表示一
9、个属性是“公有的”; (2)受保护的(protected)定义为“受保护的”属性,该类的所有子孙类都可以访问该属性,而所有非子孙类则不能访问,使用“#”号做前缀来表示一个属性是“受保护的”; (3)私有的(private)定义为“私有的”属性,只能在该类中使用,其他类都不能访问,使用“-”号做前缀来表示一个属性是“私有的”。,对于属性的类型和初始值都应该在设计模型中描述出来。类型和属性名之间用冒号隔开,等号之后写初始值。一个完整的属性定义如下: 可见性 属性名:类型 初始值,在进行属性设计时,可以参考以下一些指导原则: (1)仔细考虑分析类图中的属性,分析类的属性和软件类的属性并不都是一一对应
10、的,有时,分析类的一个属性隐含着软件类需要有一个或多个属性; (2)当考虑属性的候选类型时,可以根据实际的编程语言来确定; (3)如果一个软件类因为其属性的原因变得复杂而难于理解时,可以视具体情况将其中的一些属性分离出来形成单独的类。,设计方法 设计阶段最为重要的任务是设计软件对象所要执行的操作; 分析模型中已经大致勾画了对象行为的轮廓,在设计阶段需要对这些行为进行细化,结合用例模型和交互图来定义软件类的方法。,消息和方法 一个类的操作需要支持该类在不同用例中所扮演的所有角色,换句话说就是这个类应该承担起它应有的职责或义务; 职责的分配贯穿于交互图的整个生成过程,职责在UML中通过消息的发送分
11、配给不同的对象,消息的发送者需要某项服务,消息接收者就要提供相应的服务,即意味着接收者所要承担的职责,并最终转化为接收者的方法; 很多消息发送之后,消息的接收对象会在响应后产生一些结果回传给发送者,这就是返回消息。,具体方法: 明确职责:软件类的方法就是一个对象应该执行的操作,称这些操作为对象的职责; 职责和交互图:职责在UML中通过消息的发送分配给不同的对象,消息的发送者需要某项服务,接收者提供相应的服务,即意味着接收者所要承担的职责,并最终转化为接收者的方法; 遍历所有的交互图,发送给某个对象的所有消息的集合就表明了该对象必须定义的大多数方法。 除了从消息得到方法外,还应该注意另外一种方法
12、属性访问方法 。,遍历所有有“入库单”对象参与的用例,结合用例的事件流描述和用例的顺序图,依次找出“入库单”对象的职责,进而形成“入库单”类的方法。例如,根据“入库登记”用例可以确定“入库单”类的一个create(创建)方法,依次类推,可以确定“入库单”类的如下方法:create(创建)、find(查找)、auditing(审核)、associate(关联码单)、reckoning(平帐)、delete(删除)等 。,定义方法 和属性一样,类的方法也可以定义可见性; 在UML图中,一个方法按如下格式进行标识: 可见性 方法名(方法参数列表):类型表达式,设计关系 类之间的关系主要有三种:依赖关
13、系、泛化关系和关联关系,因此关系的设计也主要从这三种关系的设计入手; 设计依赖关系 将被依赖的类的实例作为依赖于它的另一个类的某个方法的参数;,class Person void Work(Screwdriver tool) ; class Screwdriver ;,设计泛化关系 一般在面向对象语言中使用继承来实现泛化关系,继承机制实现了子类拥有父类特性的这一过程。 设计关联关系 在关联的源类中声明一个实例属性来保存对目标类的实例的引用,这种属性称为关联属性或引用属性。,四、面向对象设计原则,单一职责原则 所谓职责,可以定义为“变化的原因”,单一职责原则描述的就是:对于一个类而言,应该仅有一
14、个引起它变化的原因; 换句话说,一个类的功能要单一,只做与它相关的事情。,开放封闭原则 “变化才是不变的真理”,我们阻止不了需求的改变,但可以通过设计使得系统能够适应改变又能保持相对稳定; 开放封闭原则的应用就是要在模块本身不变动的情况下,通过改变模块周围的环境达到修改目的; 开放封闭原则是指:软件实体(类、模块、函数等)应该是可以扩展的,但是不可以修改的;,遵循开放封闭原则设计出的模块具有两个主要特征: “对于扩展是开放的”:这意味着模块的行为是可以扩展的。当应用的需求改变时,在模块上进行扩展使其具有满足那些改变的新行为; “对于更改是封闭的”:这意味着当对模块进行扩展时,不必改动模块的源代
15、码或二进制代码。,图形(Shape)类根据ShapeType来说明图形类型,再根据不同的图形类型采用不同的公式来计算面积。当每增加一种图形时,都需要修改Shape类中的计算方法,因此Shape类对于更改就不是封闭的,这样的设计就不符合OCP原则。,将所有图形的共性抽取出来,将图形类设计成一个抽象类,定义一个抽象方法getArea,具体的圆、矩形等图形类继承于图形类,并分别实现getArea方法,从而实现计算各自面积。抽象的这一部分内容就是封闭的,扩充新的图形也不需要修改Shape类的任何内容,只需派生一个新类,并实现getArea方法就能达到扩展的目的,也就符合OCP原则,依赖倒置原则 在传统
16、的结构化编程中,最上层的模块通常都要依赖下面的子模块来实现,也称为高层依赖低层; 依赖倒置原则就是要逆转这种依赖关系,让高层模块不要依赖低层模块: 高层模块不应该依赖于低层模块,二者都应该依赖于抽象; 抽象不应该依赖于细节,细节应该依赖于抽象。,ToggleSwitch(开关)类依赖于Light类,当Light类发生变化时,势必会影响到ToggleSwitch类,例如对于不同的灯会有不同的开关方式,ToggleSwitch就要随着Light的变化而变化,这样就没有实现依赖倒置原则。,将Light设计成一个抽象类或者一个接口,让ToggleSwitch依赖于该抽象类(或接口),让具有不同开关方式的灯继承于该抽象类(或实现该接口),这样,ToggleSwitch类就不会随着灯泡类型的改变而发生变化,同时,这样的设计还满足了封闭开放原则。,Liskov替换原则 Liskov原则提供了一种解决方案来设计最佳的继承层次,使得不违反开放封闭原则。 Liskov替换原则的核心思想可以解释