
高级软件工程(第十一章)-面向对象设计方法(2017课件).ppt
33页1,1,第十一章面向对象的设计方法,2,面向对象设计的主要任务,细化重组类 细化和实现类间关系,明确其可见性 增加属性,指定属性的类型与可见性 分配职责,定义执行每个职责的方法 对消息驱动的系统,明确消息传递方式 利用设计模式进行局部设计 画出详细的类图与时序图,3,3,OO设计过程,基于领域模型,创建设计类图的基础版本或初步模型:包含属性名称等基本信息,以便用来设计交互图; 开发交互图,即为每一个用例产生一个交互图:其决定哪些对象一起工作,及怎样协同工作 (系统设计的核心); 根据开发交互图时得到的信息,迭代设计类图和开发方法名称(确定类的行为和职责) 用包图将设计类图分割成相关的功能4,4,用例的实现,称设计模型的最终开发结果为用例的实现 术语实现指的是对每个用例的详细系统过程进行说明,是制定了软件的蓝图 OO的设计也是用例驱动的也就是说,设计是由一个用例的实现接着一个用例的实现完成的 用例的实现:对每个用例的详细系统过程的说明5,用例实现方案的步骤,用例实现方案的设计方法有三个步骤: 提取边界类、实体类和控制类; 构造交互图; 根据交互图精化类图6,6,设计类图与领域模型,设计类图和详细交互图均使用对方作为设计时的输入,二者的开发过程是平行的。
设计类图是OO分析时开发的领域模型类图的扩展(分析阶段可忽略属性或方法) 领域模型揭示问题域类和它们之间的联系,是概念类模型 设计类图是领域模型的细化和扩展,是软件类模型通过集成来自交互图和其他模型的信息来完成设计类图时,还要在以前的领域模型的基础上增加很多类,7,7,标准设计类,实体类 边界类(接口类) 控制类,8,8, 实体类,实体类:是问题域中的核心类 从客观世界中的实体对象归纳和抽象出来(来自域模型)用于长期保存在系统中的信息以及提供针对这些信息的相关处理行为实体类的对象是永久性的,即持久类 持久类:程序结束后仍然存在的对象类在系统关闭后这些数据仍存在 保证持久的方法:将它们写入文件或DB实体类在DB中有相应的表,其属性对应DB中的字段9,续,实体类表示目标软件系统中具有持久意义的信息项及其操作实体类的操作具有“内向收敛”特征,它们仅向目标软件系统的其余部分提供读、写信息项内容的必要的操作接口,并不涉及业务逻辑处理10,10, 边界类(接口类),从系统和外界进行交互的对象中归纳、抽象出来 是系统内对象和系统外的参与者的联系媒介,外界消息通过边界类的对象实例发送给系统例如,窗体、对话框、报表) 边界类:存在于系统的自动化边界上的类。
11,续,边界类描述目标软件系统与外部环境的交互,主要任务: 界面控制:包括输入数据的格式及内容转换,输出结果的呈现,软件运行过程中界面的变化与切换等 外部接口:实现目标软件系统与外部系统或外部设备之间的信息交流和互操作主要关注跨越目标软件系统边界的通信协议 环境隔离:将目标软件系统与操作系统、数据库管理系统、应用服务器中间件等环境软件进行交互的功能与特性封装于边界类之中,使目标软件系统的其余部分尽可能地独立于环境软件12,12, 控制类,控制类:在边界类和实体类中间起协调作用的类,它包含了事件的应用逻辑或业务规则 它负责从边界类对象获取信息,然后发送到适当的实体类对象,就像是域层和可视层之间的一个控制器 在一个用例中,一个控制类应该同一个且仅一个参与者相关联 协调边界类和实体类之间的消息及消息发送的顺序13,续,控制类作为完成用例任务的责任承担者,协调、控制其他类共同完成用例规定的功能或行为对于比较复杂的用例,控制类通常并不处理具体的任务细节,但是它应知道如何分解任务,如何将子任务分派给适当的辅助类,如何在辅助类之间进行消息传递和协调14,提取边界类、实体类和控制类,如何从分析模型中的用例描述和领域概念模型出发获取这些类? 通常情况下,参与者与用例之间的一种通信连接对应一个边界类。
但是,如果两个以上的用例与同一参与者交互,并且这些交互具有共同的行为、完成相同或类似的任务,就可以考虑用同一边界类实现用例与参与者之间的交互这就意味着边界类的作用范围可以超越单个用例15,提取边界类、实体类和控制类,实体类源于领域模型有时也需要认真研读用例描述,从中发掘具有持久意义的信息项 如果参与者的属性需要持久保存,也可以建立相应于执行者的实体类 假设一个实体类A仅仅被系统中的另一个类B引用,并且系统勿需关心A的行为特征,那么,为了简化设计模型,应将A中信息项直接作为B的属性 如果A被系统中的多个类引用,或者A具有不容忽略的行为特征,那么应将A作为独立的实体类16,提取边界类、实体类和控制类,一个用例通常对应一个控制类 如果不同用例的任务有较多类似之处,也可以考虑在多个用例的实现方案中共享同一控制类,此种情况应审慎对待,不同用例所需要的控制、协调行为往往会有差异 对于那些事件流非常简单的用例,可以不设独立的控制类,直接在边界类中设置控制、协调功能,边界类在实体类的帮助下完成用例要求的功能及行为17,导航可见性,一个对象可看到另一个对象并与之交互(导航、可见性) 一个对象可导航(发送消息)到另一个对象。
一个对象通过发消息的方式和另一个对象进行联系,那么第二个对象对于第一个对象来说必须是可见的 对象具有能看见另一对象并与其交互的能力18,18,用例实现与交互图,用例的实现是在交互图的开发过程中完成的,用例实现的过程就是确定哪些类通过发送消息与其他类进行协作的过程 设计时开发的交互图:顺序图或协作图19,19,对象职责,一种由对象负责实施系统过程的设计准则 集中确定一个系统必须支持的行为,再设计实现这些行为的方法通过这些行为,确定对象职责20,20,用例控制器,每个用例含有许多来自外部参与者不同的消息 作为OO分析一部分的系统顺序图能够描述出这些输入消息,但只表示这些消息输入到系统中 在设计过程中,必须确定对象得到所有的消息 为简化搜集和处理用例所需要的全部消息的过程,系统设计者通常建立一个新类,该类用做输入消息的采集点,称其为用例控制器21,构造交互图,在标识边界类、实体类和控制类之后,接下来的任务是,将分析模型中的用例描述转化成UML交互图,以交互图作为用例的详细实现方案 用例描述中事件流说明中的事件应直接对应于交互图中的消息,而事件间的先后关系体现为交互图中的时序,对消息的响应则构成消息接收者的职责。
这种职责在后续的设计活动中将被确立为类的方法22,22,顺序图初步设计的规则,接受每个输入消息并确定由这个输入消息产生的所有的内部消息 在处理每个消息的时候,一定要辨别出受之影响的类的完整集合,即从领域类图中找到需要的所有对象 充实消息的结构添加迭代、真/假条件、返回值和传递参数23,构造顺序图,控制类或辅助类可以向右侧的边界类发送消息,将消息或外部处理请求由边界类传向外部系统(被动执行者) 在用例描述中,许多用例除主事件流外,往往还包含备选事件流,以说明在某些特殊或者异常情况下的事件和响应动作序列为易于理解,在设计模型中应该用分离的UML交互图分别表示事件流和每个备选事件流24,精化类图,在UML交互图中,对每个类的对象都规定了它必须响应的消息以及类的对象之间的消息传递通道前者对应于类的操作,后者则对应于类之间的连接关系因此,可以利用交互图精化分析模型中的类图,将交互图中出现的新类添加到原有类图中,并且对相关的类进行精化,定义其属性和操作 原则上,每个类都应该有一个操作来响应交互图中指向其对象的那条消息但是,这并不意味着消息与操作一定会一一对应,因为类的一个操作可能具有响应多条消息的能力。
同理,两个类之间的一条连接关系也可以为多条消息提供传递通道为了简化设计模型,也为了提高重用程度,设计人员应该尽量使用已有的操作来响应新消息,并尽量使用已存在的连接路径作为消息传递的通道如果两个类之间存在明确、自然的聚合或组合关系,则可以在类图中直接用相应的UML图元符号表示类间的聚合和组成关系,这两个关系均可提供消息传递通道25,系统中的每一个对象都应该只有一个单独的职责,而任何对象所关注的就是自身职责的完成合适的行为应该出现在合适的类里 一个类承担的职责越多,它被复用的可能性越小 类的职责主要包括两个方面:数据职责和行为职责,数据职责通过其属性来体现,而行为职责通过其方法来体现 单一职责原则是实现高内聚、低耦合的指导方针,在很多代码重构手法中都能找到它的存在,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关重构经验单一职责原则(SRP),26,开闭原则(OCP),一个软件实体应当对扩展开放,对修改关闭也就是说在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即实现在不修改源代码的情况下改变这个模块的行为。
“不能修改而可以扩展”是不允许更改系统的抽象层,而允许扩展的是系统的实现层 如何做到既不修改,又可以扩展?抽象化是开闭原则的关键让模块依赖于一个固定的抽象体,这样它就是不可以修改的;同时,通过这个抽象体派生,就可以扩展此模块的行为功能这样设计的程序只通过增加代码来变化而不是通过更改现有代码来变化27,里氏代换原则可以通俗表述为:在软件中如果能够使用基类对象,那么一定能够使用其子类对象把基类都替换成它的子类,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类的话,那么它不一定能够使用基类 里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象里氏(Liskov)替换原则(LSP),28,依赖倒置原则(DIP),高层模块不应该依赖于低层模块,二者都应该依赖于抽象 抽象不应该依赖于实现细节,实现细节应该依赖于抽象 依赖倒转原则的常用实现方式之一是在代码中使用抽象类,而将具体类放在配置文件中 依赖倒置原则指依赖关系应该是尽量依赖接口或抽象类,而不是依赖于具体类。
要针对接口或抽象类编程,而不是针对具体类编程29,接口分离原则,接口分离原则指的是在设计时采用多个与特定客户类有关的接口比采用一个通用接口更好也就是说一个类如果要被多个客户类使用,那么可以给每一个客户类创建一个接口,同时用这个类实现所有接口不要在一个接口中包含所有客户类需要的方法 客户端不应该依赖那些它不需要的接口 一旦一个接口太大,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可30,课堂,在面向对象设计中使用哪三种基本设计类?简述这三种基本设计类 什么是导航可见性?举例说明在编程中是如何实现的?,31,开闭原则练习,下图是一个针对不同打印机实现打印功能的类图设计,根据图中设计,如果现在需要添加一台Legend打印机,需做哪些修改? 上图设计并不合理,因为如果需要新加一台新的Legend打印机的话,不但要增加一个新的Legend类,还要修改Output类的内部结构不符合开闭原则 那么,应该如何改进呢?,32,上图中引入了一个接口类Printer,其中有一个print()方法Output类只与这个接口类相关联,在Output类中有一个类型为Printer的变量p。
不管系统与哪种打印机连接,实现打印功能时一律调用p.print()方法而p的具体类型在运行是由系统确定,可能是HP类型的对象,也可能是Canon类型的对象现在如果系统需要加入Legend打印机,只需增加一个Legend类,并在该类中实现Printer接口即可因此,上图具有良好的可扩展性,符合开闭原则33,图1中Class4同时被Class1,2,3调用,是一种不好的设计,应该怎样改进?,图2增加。












