文章目录
第一部分 概述
第1章 面向对象方法概论
1.1 面向对象方法定义
面向对象方法是一种运用对象、类、继承、封装、聚合、关联、消息、多态性等概念来构造系统的软件开发方法。
1.2 面向对象方法基本特点
用类和对象作为系统的基本构成单位。对象对应问题域中的事物,其属性和操作刻画了事物的静态特征和动态特征,它们之间的继承关系、聚合关系、关联和消息如实地表达了问题域中事物之间实际存在的各种关系。
1.3 面向对象的基本概念与原则
1.3.1 对象、属性、操作
对象是现实世界中某个实际存在的事物,它可以是有形的,比如一辆汽车,也可以是无形的,比如一项计划。对象是构成世界的一个独立单位。它具有自己的静态特征和动态特征。
对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。对象由一组属性和施加于这些属性一组操作构成。
属性是用来描述对象静态特征的一个数据项。
操作是用来描述对象动态特征的一个动作序列。
对象标识就是对象的名字,有“外部标识”和“内部标识”之分。
1.3.2 抽象、类、一般类、特殊类
抽象与分类:忽略事物的非本质特征,只注意那些与当前目标有关的本质特征,从而找出事物的共性,叫做抽象。抽象是形成概念的基本手段。把具有共同性质的事物划分为一类,叫做分类。
类是具有相同属性和操作的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和操作两个主要部分。类的作用是用来创建对象,对象是类的一个实例。
一般类和特殊类的定义
定义1:如果类A具有类B的全部属性和全部操作,而且具有自己特有的某些属性或操作,则A叫做B的特殊类,B叫做A的一般类。一般类与特殊类又称父类与子类。
定义2:如果类A的全部对象都是类B的对象,而且类B中存在不属于类A的对象,则A是B的特殊类,B是A的一般类。
1.3.3 封装、继承、多继承
封装:把对象的属性和操作结合成一个独立的系统单位,并尽可能隐蔽对象的内部细节。
封装的重要意义:使对象能够集中而完整地描述并对应一个具体的事物。体现了事物的相对独立性,使对象外部不能随意存取对象的内部数据,避免了外部错误对它的“交叉感染”。对象的内部的修改对外部的影响很小,减少了修改引起的“波动效应”。
继承:特殊类拥有其一般类的全部属性与操作,称作特殊类对一般类的继承。
由一组具有继承关系的类所组成的结构称作一般-特殊结构。它是一个以类为结点以继承关系为边的连通的有向图。继承关系的语义:“is a kind of"。
多继承:允许一个特殊类具有一个以上一般类的继承方式称作多继承
1.3.4 聚合与组合
聚合关系又称整体-部分关系,它是对象实例之间的一种关系。有时说两个类之间存在着整体-部分关系,是指一个类的对象实例以另一个类的对象实例作为组成部分。这种关系的语义是“has a”或“is a part of"。
由一组具有聚合关系的类所形成的结构称作整体-部分结构。它是一个以类为结点,以聚合关系为边的连通有向图。
紧密、固定的聚合方式又称为组合。
聚合与组合在UML中的表示:
1.3.5 关联
两个或者多个类上的一个关系(即这些类的对象实例集合的笛卡儿积的一个子集合),其中的元素提供了被开发系统的应用领域中一组有意义的信息。
1.3.6 消息
消息:消息是向对象发出的服务请求。目前在大部分面向对象的编程语言中,消息其实就是函数(或过程)调用。但是,函数调用只是实现消息的方式之一,上述理解只适合于顺序系统。
更一般的定义:消息是对象之间在一次交互中所传送的信息。
1.3.7 多态
多态是指同一个命名可具有不同的语义。OO方法中,常指在一般类中定义的属性或操作被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。
实现机制:
- 重写(override):在特殊类中对继承来的属性或操作重新定义其实现;
- 动态绑定(dynamic binding):在运行时根据对象接收的消息动态地确定要连接哪一段操作代码;
- 类属(generic):操作参量的类型可以是参数化的。
1.4 面向对象方法与传统方法比较
1.4.1 面向对象方法与传统方法区别
- 思想观念:面向对象方法从对象出发认识问题域;
- 构造策略:面向对象方法以对象作为构成系统的基本单位,将对象的数据与操作紧密结合;
- 保证机制:面向对象方法由封装、继承、多态等机制保证其原则的实现。
1.4.2 面向对象方法的主要优点
- 从认识论的角度面向对象方法改变了开发软件的方式。
- 面向对象语言使得从客观世界到计算机的语言鸿沟变窄。
- 面向对象方法使得分析与设计之间的鸿沟变窄。
- 面向对象方法有助于软件的维护与复用。
- 面向对象方法有助于提高软件的质量和生产率。
1.5 思考题
1.5.1 与传统方法相比,面向对象方法有什么优点?
答:
- 从认识论的角度面向对象方法改变了开发软件的方式。
- 面向对象语言使得从客观世界到计算机的语言鸿沟变窄。
- 面向对象方法使得分析与设计之间的鸿沟变窄。
- 面向对象方法有助于软件的维护与复用。
- 面向对象方法有助于提高软件的质量和生产率。
1.5.2 封装的目的是什么?在面向对象方法中封装的目的是如何达到的?
答:
目的:降低对象之间的耦合度,避免外部错误对它的“交叉感染”;这样对象内部修改对外部的影响变小,减少了修改引起的“波动效应”。
如何达到:通过类的访问控制。
1.5.3 什么是面向对象方法?
答:
面向对象方法是一种运用对象、类、继承、封装、聚合、关联、消息、多态性等概念来构造系统的软件开发方法。
1.5.4 面向对象的基本思想?
答:
- 从现实世界中客观存在的事物出发来建立软件系统,根据事物的本质特征,抽象为系统中的对象,作为系统的基本构成单位。
- 用对象的属性来表示事物的性质,用对象的操作来表示事物的行为。
- 对象的属性和操作结合为一体,对外屏蔽其内部细节。
- 对事物进行分类。
- 复杂的对象可以用简单的对象进行组合和聚合。
- 对对象进行不同程度的抽象,得到一般类和特殊类。
- 对象之间通过消息进行通信。
1.5.5 简述面向对象技术的三大机制。
答:
- 封装:将对象的属性和行为结合成为一个独立的单位,对外屏蔽其内部细节。
- 继承:特殊类拥有其一般类的全部属性与操作,称作特殊类对一般类的继承。
- 多态:指在一般类中定义的属性或操作被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。可以通过重写、动态绑定、类属的机制来实现。
第二部分 面向对象分析
第2章 不同的分析与设计方法
2.1 历史上几种建模方法
2.1.1 功能分解法
首先定义各种功能,然后把功能分解为子功能,同时定义功能/子功能之间的接口。对较大的子功能进一步分解,直到可给出明确的定义。
优点:
- 直接地反映用户的需求,所以工作很容易开始。
- 适用于功能稳定的领域。
缺点:
- 不能直接地映射问题域,很难检验结果的正确性。
- 对需求变化的适应能力很差。
- 局部的错误和修改很容易产生全局性的影响。
2.1.2 数据流法(结构化分析)
其基本策略是跟踪数据流,即研究问题域中数据如何流动,以及在各个环节上进行何种处理,从而发现数据流和加工。得到的分析模型是数据流图(DFD),主要模型元素是数据流、加工、文件及端点,外加处理说明和数据字典。
优点:
- 有严格的法则,强调研究问题域
缺点:
- 仍然是间接映射问题域;
- 与结构化设计的概念不一致,从分析到设计的过渡比较困难;
- 数据流和加工的数量太多,引起分析文档的膨胀。
2.1.3 信息建模方法
由实体-关系法(E-R方法)发展而来。核心概念是实体和关系。实体描述问题域中的事物,关系描述事物之间在数据方面的联系,都可以带有属性。发展之后的方法也把实体称作对象,并使用了类型和子类型的概念,作为实体(对象)的抽象描述。
优点:
- 有关系数据库的良好数学基础,可以保证系统的数据完整性、一致性;
- 适合以数据处理为中心的软件系统;
缺点:
- 对功能的刻画能力较弱。
2.1.4 面向对象方法
运用对象、类、继承、封装、聚合、关联、消息、多态性等概念来构造系统。把问题域中的事物抽象为对象,作为系统的基本构成单位其属性和操作刻画了事物的静态特征和动态特征——完整地刻画了问题域中事物。
用类作为对象的抽象描述,建立它们之间的继承、聚合、关联、消息等关系——如实地表达了问题域中事物之间的各种关系。
封装、继承、聚合、关联、消息通讯等原则符合人类的日常思维——使系统的复杂性得到控制。
因此,得到的系统模型可以直接映射问题域。
2.2 OOA模型
顾名思义,面向对象的分析(OOA),就是运用面向对象方法进行系统分析。首先,OOA是分析,是软件生命周期的一个阶段,具有一般分析方法共同具有的内容、目标及策略;但是,它强调运用面向对象方法进行分析,用面向对象的概念和表示法表达分析结果。基本任务是运用面向对象方法,对问题域和系统责任进行分析和理解,找出描述问题域及系统责任所需的对象,定义对象的属性、操作以及它们之间的关系。目标是建立一个符合问题域、满足用户需求的O0A模型。
2.3 OOD过程
第3章 用况图
3.1 系统边界与参与者
系统边界:一个系统所包含的所有系统成分与系统以外各种事物的分界线。
参与者:在系统边界以外,与系统进行交互的事物人员、设备、外系统。
3.2 用况
3.2.1 用况:
用况是对参与者使用系统的一项功能时所进行的交互过程的描述,其中包含由双方交替执行的一系列动作。
几点说明:
(1)一个用况只描述参与者对单独一项系统功能的使用情况;
(2)通常是平铺直叙的文字描述,UML也允许其他描述方式;
(3)陈述参与者和系统在交互过程中双方所做的事;
(4)所描述的交互既可能由参与者发起也可能由系统发起 ;
(5)描述彼此为对方直接地做什么事,不描述怎么做;
(6)描述应力求准确,允许概括,但不要把双方的行为混在一起;
(7)一个用况可以由多种参与者分别参与或共同参与;
启动用况:外部驱动力、满足前置条件——用况启动的前提
用况执行结果:用况执行完了,会有一个结果,这称为用况的后置条件
3.2.2 用况与参与者之间的关系
用况与参与者间的关联是参与者在用况中的参与(也就是参与者实例与用况实例之间的相互通信)。若没有进行特殊的说明,任何一方都可发送和接收消息。即交互是双向的,参与者能够产生对系统的请求,或系统要求参与者采取某些动作。这是参与者和用况之间的唯一关系。
3.2.3 用况之间的关系
(1)扩展关系:用带箭头的虚线加版型来表示
表示用况场景中的某个“支流”,由特定的扩展点触发而被启动。此时扩展表示“可选”,而不是“必需”,没有扩展用况,基本用况也是完整的;如果没有基本用况,扩展用况是不能单独存在的;如果有多个扩展用况,同一时间用况实例也只会使用其中的一个。
何时使用“扩展用况”:
a.表明用况的某一部分是“可选”的系统行为。
b.表明只在特定条件下才执行分支流。
c.表明有一组行为段,其中的一个或多个段可以在基本用况的扩展点处插入。
d.表明多个基本用况的可复用部分。
(2)包含关系:用一条带箭头的虚线加版型表示,特别用于用况模型,说明执行基本用况实例过程中插入的行为段。
包含用况代表在各种不同基本用况中复用的行为,包含用况表示的是“必需”而不是“可选”的行为;如果没有包含用况,基本用况是不完整的,如果没有基本用况,包含用况是不能单独存在的;基本用况可以控制与包含用况的关系,并依赖于执行包含用况所得的结果,基本用况与包含用况都不能访问对方的属性。
何时使用“包含用况”:
a.从基本用况中分解出的行为,它不需要了解基本用况,但是要向基本用况返回它执行的结果。
b.两个或更多的用况能提炼出共同的行为。
(3)泛化关系:用况之间的泛化关系就像类之间的泛化关系一样,子用况继承父用况的行为和含义。用一个指向父用况的带有封闭的空心箭头的实线来表示用况之间的泛化关系。
3.2.4 捕获用况
-
利用参与者捕获用况
-
从系统功能角度捕获用况
(1)以穷举的方式检查用户对系统的功能需求是否能在各个用况中体现出来。
(2)以穷举的方式考虑每一个参与者与系统的交互情况,看看每个参与者要求系统提供什么功能,以及参与者的每一项输入信息将要求系统作出什么反映,进行什么处理。
(3)考虑对例外情况的处理。针对用况描述的基本流,要详尽地考虑各种其他的情况
(4)一个用况描述一项功能,这项功能不能过大。例如,把一个企业信息管理系统粗略地分为生产管理、供销管理、财务管理和人事管理等几大方面的功能,就显得粒度太大了,应该再进行细化。
(5)一个用况应该是一个完整的任务,通常应该在一个相对短的时间段内完成。如果一个用况的各部分被分配在不同的时间段,尤其被不同的参与者执行,最好还是将各部分作为单独的用况对待。 -
使用场景技术
3.2.5 用况图示例
很多软件系统在一开始都需要登录,若用户登录成功,则可进入系统。如下以一个研究生学籍管理系统为例,描述四种登录方法。为了简化起见,假设此处仅描述登录、选课和查看学分这3项功能。
方案一:
由于选课和查看学分都需要登录,故专门设立一个“登录”用况。若登录成功,则可以进行选课,也可以进行查看学分。
登录的描述:
研究生启动系统;
系统提示研究生输入研究生证号和密码;
研究生输入研究生证号和密码;
系统进行验证,给出验证信息;
若通过,若该生选择选课
系统执行用况“选课”;
若通过,若该生选择查看学分
系统执行用况“查看学分”;
该方法的缺点是,
(1)必须要了解系统的所有其它模块,才能描述清楚“登录”用况。向系统增加新用况时,也要修改登录用况。从维护的角度看,有时会忘记对“登录”用况进行修改。
(2)“登录”用况的功能不单一。
方案二:
让所有的相关用况都包含登录用况。
如下为对用况“选课”的描述:
研究生启动系统,调用用况“登录”
若通过,系统执行用况“选课”的其余部分;
如下为对用况“查看学分”的描述:
研究生启动系统,调用用况“登录”
若通过,系统执行用况“查看学分”的其余部分;
其缺点为,对研究生要进行多次验证。——研究生执行系统的每项功能都要先登录。
方案三:
使用扩展,设计系统登录。
如下为对用况“登录”的描述:
研究生启动系统;
系统提示研究生输入研究生证号和密码;
研究生输入研究生证号和密码;
系统进行验证,给出验证信息;
若通过,若该生选择选课
系统在扩展点”选课”处执行用况“选课”;
若通过,若该生选择查看学分
系统在扩展点”查看学分”处执行用况“查看学分”;
该方案与方案一相比,对“登录”用况的描述要清楚一些。在增加新用况时,仅在登录用况中添加扩展点即可。
缺点:“登陆”用况的功能仍不单一。
方案四:
登录用况完全独立于其它用况。
如下为对用况“登录”的描述:
研究生启动系统;
系统提示研究生输入研究生证号和密码;
研究生输入研究生证号和密码;
系统进行验证,给出验证信息;
如下为对用况"选课”的开始部分的描述:
若研究生通过了登录且选择了选课,
系统开始执行用况“选课”;
使用该方法,必须要在“选课”用况和“查看学分”用况中指定前置条件:只有在登录成功后才能执行自己。
3.3 习题
3.3.1 用况之间包含关系、扩展关系与泛化关系有相同之处吗?
答:
有相同之处。这三种关系都是将用况中的一些动作序列抽取出来,放到另外一个用况中,使得用况的描述和用况之间的关系更加明确,提高复用性。
3.3.2 论述用况图在面向对象方法中的地位。
答:
用况图用于对系统的功能以及与系统进行交互的外部事物建模。通过找出与系统交互的外部事物,并说明它们如何与系统交互,易于对系统进行探讨和理解。这样,用户能够理解未来的系统,开发者也能够正确地理解需求并实现系统。用况图是对所捕获的需求的规范化描述,是进行面向对象分析的基础,对面向对象设计阶段的人机交互设计和系统测试来说,也是十分重要的。
3.3.3 通常自动售货机会按用户的要求进行自动售货。供货员会巡查向其内供货,取款员会定时取款。请建立用况图,并描述各个用况。
答:
第4章 类图
4.1 概念
4.1.1 对象
对象(object)是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位,由一组属性和施加于这组属性的一组操作构成。
主动对象( active object)——至少有一个操作不需要接收消息就能主动执行的对象,用于描述具有主动行为的事物
被动对象(passive object) ——每个操作都必须在消息的驱动下才能执行的对象。
4.1.2 类
类(class)是具有相同属性和操作的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,它由一个类名、一组属性和一组操作构成。
类和对象的关系——集合与成员,对象是类的实例。
不直接创建对象实例的类称为抽象类(abstract class)
主动对象的类叫做主动类( active class)
4.2 发现对象(理解即可)
4.2.1 研究问题域
- 亲临现场深入调查研究
直接观察并向用户及相关的业务人员进行调查和交流,考察问题域中各种各样的事物、它们的特征及相互关系 - 听取问题域专家的见解
领域专家——包括技术人员、管理者、老职员和富有经验的工人等 - 阅读相关材料
阅读各种与问题域有关的材料,学习相关行业和领域的基本知识 - 借鉴以往的系统
查阅以往在该问题域中开发过的同类系统的分析文档,吸取经验,发现可以复用的类
4.2.2 正确地运用抽象原则
- 对什么进行抽象——问题域
- 当前目标——系统责任
- 忽略与系统责任无关的事物,只注意与之有关的事物,抽象为系统中的对象
例如:学校的教师、学生、教务员和警卫忽略与系统责任无关的事物特征 - 只注意与之有关的特征,抽象为对象的属性或操作
例如:教师的专业、职称和身高、体重 - 正确地提炼对象
例如:对书的不同抽象,在图书馆管理系统中以一本书作为一个对象实例,在书店管理系统中以一种书作为一个对象实例
4.2.3 策略与启发
(1)考虑问题域
(2)考虑系统边界
考察在系统边界以外与系统交互的各类参与者考虑通过那些对象处理这些参与者的交互
(3)考虑系统责任
检查每一项功能需求是否已有相应的对象提供,发现遗漏的对象
4.2.4 审查与筛选
(1)舍弃无用的对象
- 通过属性判断:是否通过属性记录了某些有用的信息?
- 通过操作判断:是否通过操作提供了某些有用的功能?
二者都不是——无用
(2)对象的精简
(3)与实现条件有关的对象
例如:与图形用户界面(GUI)数据管理系统硬件及操作系统有关的对象推迟到OOD考虑。
4.3 对象分类(理解即可)
4.3.1 将对象抽象为类,用类表示它的全部对象
4.3.2 审查和调整
- 类的属性或操作不适合该类的全部对象实例
例:“汽车”类的“乘客限量”属性——进一步划分特殊类 - 属性及操作相同的类
经过抽象,差别很大的事物可能只保留相同的特征—考虑能否合并为一个类 - 属性及操作相似的类
—考虑能否提升出一个一般类 - 同一事物的重复描述
例:“职员”和“工作证”——取消其中一个
4.3.3 类的命名
- 类的名字应适合该类(及其特殊类)的全部对象实例
- 反映个体而不是群体
- 使用名词或带定语的名词
避免市井俚语和无意义的符号使用问题域通用的词汇 - 使用便于交流的语言文字
可以用本地文字和英文双重命名
4.4 定义属性(理解即可)
4.4.0 属性
属性是用来描述对象静态特征的一个数据项。
实例属性(instance attribute)和类属性(class attribute)的区别
例如:
仪表类输入电压、功率及各种规定的质量指标——类属性
编号、出厂日期、精度等实际性能参数——实例属性
4.4.1 策略与启发
- 按常识这个对象应该有哪些属性?
人→姓名、地址、出生年月 - 在当前的问题域中,对象应该有哪些属性?
商品→条形码 - 根据系统责任,这个对象应具有哪些属性?
乘客→手机号码 - 建立这个对象是为了保存和管理哪些信息?
物资→型号、规格、库存量 - 为实现操作的功能,需要增设哪些属性?
传感器(信号采集功能)→时间间隔 - 是否需要增加描述对象状态的属性?
设备→状态 - 用什么属性表示关联和聚合?
课程→任课教师,汽车→发动机
4.4.2 审查与筛选
- 是否体现了以系统责任为目标的抽象
例:书→重量? - 是否描述对象本身的特征
例:课程→电话号码?是否可通过继承得到? - 是否可从其他属性直接导出?
4.4.3 推迟到OOD考虑的问题
规范化问题
对象标识性能问题
4.4.4 属性的命名与定位
命名:原则与类的命名相同
定位:针对所描述的对象,适合全部对象实例
4.5 定义操作
4.5.0 操作
操作是用来描述对象动态特征(行为)的一个动作序列。
被动操作(passive operation) :
只有接收到消息才能执行的操作编程语言中的函数、过程等被动成分
主动操作( active operation) :
不需要接收消息就能主动执行的操作编程语言中的进程、线程等主动成分
4.5.1 对象行为分类
- 系统行为——不需要定义
例:创建、删除、复制、转存 - 封装行为引起的附加行为——不需要定义
例:读、写属性值 - 对象自身的行为——努力发现
计算或监控
4.5.2 策略与启发
- 考虑系统责任
有哪些功能要求在本对象提供? - 考虑问题域
对象在问题域对应的事物有哪些行为? - 分析对象状态
对象状态的转换是由哪些操作引起的? - 追踪操作的执行路线
模拟操作的执行,并在整个系统中跟踪
4.5.3 审查与调整
- 审查对象的每个操作是否真正有用
是否直接提供系统责任所要求的某项功能?
或者响应其它操作的请求间接地完成这种功能的某些局部操作? - 调整——取消无用的操作
- 审查操作是不是高内聚的
一个操作应该只完成一项单一的、完整的功能 - 调整——拆分或合并
4.5.4 认识对象的主动行为
- 考虑问题域
对象行为是被引发的,还是主动呈现的? - 与参与者直接交互的对象操作
- 操作执行路线逆向追踪
4.5.5 操作过程描述——可以用流程图|或活动图
4.5.6 操作的命名和定位
- 命名:动词或动宾结构
- 定位:
与实际事物一致,例:售货员——售货,商品——售出。
在一般–特殊结构中的位置———适合类的全部对象实例。
从主语-谓语-宾语结构看对象操作的设置。
4.5.7 接口
接口( interface)是由一组操作所形成的一个集合,它由一个名字和代表其中每个操作的特征标记构成。
特征标记(signature)代表了一个操作,但并不具体地定义操作的实现
特征标记:=<操作名>(|<参数>:<类型> {,参数>:<类型>}) [:<返回类型>]
表示方法:
接口与类的关系:
接口由某些类实现(提供),由另外某些类使用(需要),前者与接口的关系称为实现(realization),后者与接口的关系称为使用(use)
接口与类的区别:
- 类既有属性又有操作;
接口只是声明了一组操作,没有属性。 - 在一个类中定义了一个操作,就要在这个类中真正地实现它;
接口中的操作只是一个声明,不需要在接口中加以实现。 - 类可以创建对象实例;
接口则没有任何实例。
引入接口概念的好处:
在接口的使用者和提供者之间建立了一种灵活的衔接机制,有利于对类、构件等软件成分进行灵活的组装和复用。
将操作的声明与实现相分离,隔离了接口的使用者和提供者的相互影响。使用者只需关注接口的声明,不必关心它的实现;提供者不必关心哪些类将使用这个接口,只是根据接口的声明中所承诺的功能来实现它,并且可以有多种不同的实现。接口概念对描述构件之间的关系具有更重要的意义
4.6 定义类间关系
4.6.1 一般-特殊关系(is a kind of)
如何发现?
- 学习当前领域的分类学知识
- 按常识考虑事物的分类
- 根据一般类和特殊类的两种定义
- 考察属性与操作的适应范围
- 考虑领域范围内的复用
审查与调整
- 问题域是否需要这样的分类?(例:书—线装书)
- 系统责任是否需要这样的分类?(例:职员—本市职员)
- 是否符合分类学的常识?(用“is a kind of ”来衡量)
- 是否真正的继承了一些属性或操作?
简化
- 取消没有特殊性的特殊类
- 增加属性简化一般一特殊结构
- 取消用途单一的一般类,减少继承层次
一般类存在的理由:
有两个或两个上以上的特殊类
需要用它创建对象实例
有助于软件复用
多继承:
允许一个特殊类具有一个以上一般类的继承模式
多态:
多态是指同一个命名可具有不同的语义。OO方法中,常指在一般类中定义的属性或操作被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。
4.6.2 整体-部分结构(a part of)
在连接符两端通过数字或者符号给出关系双方对象实例的数量约束,称为多重性(multiplicity)
- 确定的整数——给出确定的数量—例如:1,2
- 下界…上界——给出一个范围——例如:0…1,1…4
- * ——表示多个,数量不确定
- 下界…* ——表示多个,下界确定——例如0…*, 1…*
如何发现?
考察问题域中各种具有构成关系的事物
- 物理上的整体事物和它的组成部分;例:机器、设备和它的零部件
- 组织机构和它的下级组织及部门;例:公司与子公司、部门
- 团体(组织)与成员;例:公司与职员
- 一种事物在空间上包容其它事物;例:生产车间与机器
- 抽象事物的整体与部分;例:学科与分支学科、法律与法律条款
- 具体事物和它的某个抽象方面;例:人员与身份、履历
审查和筛选
(1)是否属于问题域? 例:公司职员与家庭
(2)是不是系统责任的需要? 例:员工与工会
(3)部分对象是否有一个以上的属性? 例:汽车与车轮(规格)
(4)是否有明显的整体-部分关系? 例:学生与课程
高级技巧
(1)简化对象的定义
(2)支持软件复用
(3)表示数量不定的组成部分
(4)表示动态变化的对象特征
4.6.3 关联
(1)带有属性和操作的关联
问题:增加了概念的复杂性,缺乏编程语言支持
解决方法:
复杂关联表示法的转换
(2) n元关联
在模型中,把n元关联定义为一个类,并定义它与原有的各个类之间的关系——都是二元关联。
(3)一个类在一个关联中多次出现
例:课程实习中每两名学生在一台设备上合作完成一个题目。
1)若系统要求记录和查阅哪两名学生是合作者
建立学生类到它自身的关联(如同城市之间有航线)是一个二元关联,其中学生类在关联中出现了两次
2)如果还要记录每组学生的实习题目和使用的设备
建立学生、题目、设备三个类之间的4元关联学生类在这个关联中出现了两次
加入该系统的多重性要求是:
- 每两名学生在一台设备上合作完成一个题目;
- 一个题目可以供多组学生实习,可以在不同的设备上完成;
- 一台设备可以供多组学生使用,可以做不同的题目。
4.6.4 消息
(1)什么是消息( message)
- 现实生活中——人或其他事物之间传递的信息,
例如:人与人之间的对话、通信、发通知、留言;交通信号灯对车辆和行人发出的信号;人发给设备的遥控信号等… - 软件系统中——进程或软件成分之间传送的信息
控制信息,例如一次函数调用
数据信息,例如传送一个数据文件 - 面向对象的系统中——(按严格封装的要求)消息是对象之间在行为上的唯一联系方式
消息是向对象发出的服务请求(狭义)
消息是对象之间在一次交互中所传送的信息(广义) - 消息有发送者和接收者,遵守共同约定的语法和语义
UML对各种箭头的用法:
4.6.5 依赖
继承、聚合、关联、消息这四种关系中都已经蕴涵了依赖的含义,不需要再用依赖关系再做重复的表示,除了上述关系之外,在OO建模中没有多少重要的信息必须用依赖关系表达
4.7 思考题
4.7.1 论述类与对象之间的关系以及关联与链之间的关系。这些概念之间还有什么关系吗?
答:
对象是具有明确语义边界并封装了状态和行为的实体,由一组属性和作用在这组属性上的一组操作构成;类是对具有相同属性和操作的一组对象的抽象描述,也就是说它为属于该类的全部对象提供了统一的抽象描述。对象是类的实例。
关联是两个或两个以上类间的一种关系,其中的元素提供了被开发系统的应用领域中的一组有意义的信息。如果类的对象之间通过属性有连接关系,那么这些类之间的语义关系就是关联。链表示对象间的物理与概念联结。链是关联的实例,关联是链的抽象。
关联是类之间的静态联系,链是对象之间的静态联系。两个存在有关联关系的类被实例化成对象后,类之间的关联就被实例化为链。
4.7.2 针对下述问题,建立一个类图:有两种顾客,一种是常客,享受公司的一些优惠待遇;另一种是散户。
答:
4.7.3 面包是由面包片组成的。讨论面包及其切片之间的关系。
答:
面包与其切片之间是组合的关系。
组合是聚合的一种形式,它要求一个部分类的对象在一个时刻至多属于一个整体类的对象,且整体类的对象管理它的部分类的对象。
面包与面包片是“整体—部分”的关系;另一方面,一片面包片只能属于一个面包,当这个面包“消亡”(被吃掉)后,面包片也就不存在了。因此应该是组合关系。
4.7.4 在类图中,主要的类间关系有哪几种,各代表什么含义?
答:
关联、聚合、组合、泛化、依赖。
- 关联:表示一个类对另一个类的静态依赖关系。
- 聚合:表示两个类之间存在整体和部分的关系。(部分和整体的生命周期未必一致)
- 组合:表示两个类之间存在更为紧密的整体和部分关系。(整体与部分共存亡)
- 泛化:表示两个类之间是一般和特殊的关系。
- 依赖:表示一个类需要依靠另一个类来实现功能。
第5章 建立辅助模型
5.1 顺序图(时序图)
5.1.1 概念
顺序图是一种详细地表示对象之间行为关系的图。它按时间顺序展现了一组相互协作的对象在完成一项功能时所执行的操作,以及它们之间所传送的消息,从而清晰地表示对象之间的行为关系以及操作和消息的时序关系。
顺序图是二维的:垂直方向表示时间,水平方向表示不同的对象或参与者。
通常时间维由上到下(根据需要,也可以由下到上)。通常只有时间顺序是重要的,但在实时应用中时间轴是能度量的。对象的水平顺序并不重要,顺序可以是任意的。
例子:
5.1.2 对象生命线
把对象表示成称之为“生命线”的垂直虚线。生命线代表一个对象在特定时间内的存在。
在图的顶部(第一个箭头之.上)放直在文互开始时就存在的对象,而在整个交巨完成时仍然存在的对象的生命线,要延伸超出最后一个箭头。
如果一个对象在图中所规定的时|间段被创建,那么就把创建对象的箭头的头部在对象符号上。如果对象在图中被销毁,那么用—个大的“X”标记它的析构,该标记或者放在引起析构的箭头处,或者放在从被销墅的对象最终返回的箭头处(在自析构的情况下)。
生命线可以分裂成两条或更多条并发的生命线,以表示条件性。这样的每一个生命线对应于交互中的一个条件分支。生命线可以在某个后续点处合并。
5.1.3 执行规约
执行规约表示一个对象直接或者通过从属例程执行一个行为的时期。它既表示了行为执行的持卖时间,也表示了调用者与被调用者之间的控制关系。
用一个窄长的矩形表示执行规约,矩形顶端和它的开始时刻对齐,未端和它的结束时刻对齐。
执行规约符号的顶端画在进入的箭头的端(开始该动作的那个前头),低端画在返回的箭头的尾部。
当一个对象处于执行规约期时,该对象能够响应或发送消息,执行对象或活动。
当一个对象不处于执行规约期时,该对象不做什么事情,但它是存在的,等待新的消息执行规约它。
若调用一个对象的另一个操作,第二个执行规约符号画在第一个符号稍微靠右的位置。
5.1.4 消息
消息是对象之间的通讯的规格说明,这样的通讯用于传输将发生的活动所需要的信息——控制信息(如调用)和所使用的数据的规格说明。
一个消息会调用另一个对象的操作,调用本对象的操作,向另一个对象发送一个信号,创建或者撤消一个对象(可以自己销毁自己),还可能向调用者返回一个结果。
把消息表示为从一个对象生命线到另一个对象生命线的一个水平实线箭头,即从源对象指向目标对象,以触发目标对象中的特定操作。对于对象到自身的消息,箭头就从同一个对象符号开始和结束。
用消息(操作或信号)的名字及其参数值或者参数表达式标示箭头。
消息分支
把分支画成从一个点出发的多个箭头,每个箭头由监护条件标示。依据监护条件是否互斥,这个结构可以表达条件或者并发。
消息循环
标以持续的条件:*[条件]方框围起来的区域为重复的。
5.1.5 顺序图中的结构化控制
序列性的消息能很好地说明单一的线性的序列,但是我们通常需要展示条件和循环。有时候我们想要展示多个序列的并行执行。在顺序图中用结构化控制操作符能展示这种高层控制。
为了表示顺序图的边界,可以把顺序图用一个封闭的矩形包围起来,并在矩形的左上角放一个小五边形。在这个小五边形内先写上sd,再后面写出图的名字。
对每个子顺序图加上一个矩形区域作为外框,再在其左上角放一个小五边形,在这个小五边形内写上用来表明控制操作符的类型的文字。
-
可选执行标签是opt。如果控制进入该操作符标识的交互区域时监护条件成立,那么执行该交互区域。监护条件是一个用方括号括起来的布尔表达式,它要出现在交互区域内部第一条生命线的顶端,在其中可以引用该对象的属性。
-
条件执行标签为alt。用水平虚线把交互区域分割成几个分区,每个分区表示一个条件分支并有一个监护条件。如果一个分区的监护条件为真,就执行这个分区,但最多只能执行一个分区。如果有多于一个监护条件为真,那么选择哪个分区是不确定的。若没有应对措施,在模型中要避免这种情况。如果所有的监护条件都不为真,那么控制流将跨过这个交互区域而继续执行。其中的一个分区可以用特殊的监护条件[else],这意味着如果其他所有区域的监护条件都为假,就执行该分区。
-
并行执行标签是par。用水平虚线把交互区域分割为几个分区。每个分区表示一个并发计算。当控制进入交互区域时并发地执行所有的分区;在并行分区都执行完后,那么该并行操作符标识的交互区域也就执行完毕。每个分区内的消息是顺序执行的。需要指出的是,并发并不总是意味着物理上的同时执行。并发其实是说两个动作没有协作关系,而且可按任意次序发生。如果它们确实是独立的动作,那么它们还可以交叠。
-
循环(迭代)执行标签是loop。在交互区域内的顶端给出一个监护条件。只要在每次迭代之前监护条件成立,那么循环主体就会重复执行。一旦在交互区域顶部的监护条件为假,控制就会跳出该交互区域。
5.1.6 建立顺序图
- 决定为系统建立哪些顺序图
基本以用况为单位,但是不绝对;简单的用况不必用顺序图描述,系统内部的功能也可以用顺序图描述。 - 确定参加交互的对象和参与者
确定参加交互的参与者,找出与参与者直接交互的对象;以消息为线索,找出与交互有关的全部对象 - 顺序图的绘制
5.2 通信图(协作图)
5.2.1 概念
“通信图集中于生命线之间的交互,中心问题是其内部组织的体系结构以及如何与消息传输协调。消息的次序通过其序列号给出。”
“通信图与简单的顺序图是一致的,简单的顺序图是指,没有交互使用和组合片段等结构机制,并且假设不会发生消息超越(即在一个给定的消息集合内接收消息的次序与发送消息的次序不同)或者与之无关。”
5.2.2 表示
5.3 活动图
5.3.1 概念
活动图是一种描述系统行为的图,它把一项行为表示成一个可以由计算机、人或者其他执行者执行的活动,通过给出活动中的各个动作以及动作之间的转移关系来描述系统的行为。
活动图由结点(node)和边(edge)两种基本元素构成。
- 活动结点——动作、判断、合并、分岔、汇合、起点、结束活动
- 边——控制流和对象流
5.3.2 基本元素
- 判断与合并——是一对控制结点
判断( decision)表示执行到这一点时将判断是否满足某些条件,以决定从不同的分支选择下一个动作。合并(merge)表示把多个分支合并到一起。
- 分岔与汇合——另一对控制结点,用来表示并发行为
分岔(fork)表示一旦前面的动作结束而流入这个结点,它的每个流出边所指的动作都可以执行。
汇合(join)表示汇合点之前有多个控制流在汇合点上需要取得同步,并汇合为一个控制流。
- 起点、活动结束和流结束
起点(initial node)表示由一个活动图所描述的整个活动的开始;
活动结束(activity final)表示活动图所描述的整个活动到此终结;
流结束(flow final)表示活动图中一个控制流的终结,但并不是整个活动终结。
- 活动边
连接两个活动结点的有向边称为活动边( actiivity edge),包括控制流(control flow)和对象流(object flow)
- 泳道(swim lane)
一种辅助机制,其作用是把活动图中的各个动作划分到与它们的执行者相关的若干区域中,从而清晰地表现出不同的执行者分别执行了哪些动作。
5.3.3 例子
- 订单处理活动图
- 个人信息管理活动图
5.4 状态图
5.4.1 概念
状态图:
状态图是一种描绘系统中的对象(或者其他实体)在其生命期内所经历的各种状态、状态之间的转移、发生转移的动因、条件及活动的模型图。
状态(state):
“对象生命期中的一种条件或者情形,在此期间它满足某些条件,执行某些活动,或者等待某些事件。”“状态是对一种状况的模型表示,在此期间保持了某些(通常是固有的)条件。”
伪状态(pseudo state):
伪状态实际上并不是一种状态,只是为了加强状态机图的可视化效果而引入的一些图形符号,都是结点(顶点)型的图形成分。
转移(transition):
“源顶点和目标顶点之间的一个有向的关系。”
组合状态(composite state):
由若干状态组织在一起所形成的状态。包含在组合状态内部的状态称为子状态内部不包含其他状态的状态称为简单状态
5.4.2 示例
5.5 包图
5.5.1 概念
包(package)是一种将其他模型元素组织起来,形成较大粒度的系统单位的通用机制。
5.5.2 包之间的关系
- 引入(import)是包之间的一种依赖关系,表明源包中的模型元素能够直接引用目标包中的模型元素。
- 访问(access)关系与引入关系类似,二者之间的差别只是被引入的目标有不同的可见性。
- 合并(merge)表示概念定义被合并到源包中。
5.5.3 建立包图
- 将模型元素打包
根据类图中的各种关系:一般-特殊结构,整体-部分结构,关联,消息其他关系
根据问题域和用况:对象来源,功能类别,通信频繁程度,并发和分布情况
- 包的命名
一般-特殊结构中的根类,整体-部分结构中的整体对象类,其他:事物、功能、来源 - 组织嵌套的包
7士2原则
将若干低层包合并为高层包,将低层的包与零散模型元素组织到高层的包中
合并的依据:参照将模型元素打包的考虑因素,包之间内容是否有交叉,包之间的关系是否紧密 - 减少包的嵌套层次
5.6 习题
5.6.1 使用信用卡可以在AMT机上进行取款,针对一次取款,建立类图、顺序图。注意ATM机是与银行连网的。
要求:
(1)绘制一个类图(不要过于复杂)
(2)绘制顺序图
答:
(1)
(2)
5.6.2 几台计算机共用一台打印机,打印机由打印服务器管理,请建立顺序图。
答:
5.6.3 为简易的电子表建立状态机图。
答:
5.6.4 在图书馆中,购入的书在半个月内为新书,以后为旧书。书无论新旧,都可以向外借阅。针对上述要求建立状态机图。
答:
5.6.5 针对简易电梯,建立状态机图。
答:
第三部分 面向对象设计
第6章 什么是面向对象设计
6.1 OOA与OOD的关系
- 从OOA到OOD不是转换,而是调整和增补,将OOA模型搬到OOD作为OOD模型的问题域部分。
- 采用一致的概念和表示法
- 有不同的侧重点和不同的策略:OOA主要针对问题域,OOD主要解决与实现有关的问题。
- OOA与OOD可适合不同的生命周期模型。
6.2 OOD模型与OOD过程
6.2.1 OOD模型
包括五个方面:
- 问题域部分的设计
- 人机交互部分的设计
- 控制流管理部分的设计
- 数据管理部分的设计
- 构件部署设计
6.2.2 OOD过程
OOD过程与OOD模型中五个部分相对应的五项活动组成
第7章 问题域部分设计
7.1 复用类
如果已存在一些可复用的类,而且这些类既有分析、设计时的定义,又有源程序,那么,复用这些类即可提高开发效率与质量。
目标:尽可能使复用成分增多,新开发的成分减少
例如:
7.2 增加一般类以建立共同协议
7.2.1 概念
一般类:包含多个类中一般特征的类
共同协议:多个类具有的共同特征(属性和操作)
7.2.2 适用情况
- 增加一个类,将有共同属性和操作的类组织在一起,提供通用的协议。
- 增加一般类,提供局部通用协议。
- 对相似操作的处理
7.3 提高性能
- 调整对象分布
- 增加保存中间结果的属性或类
- 为提高或降低系统的并发度,可能要人为地增加或减少主动类
- 合并通信频繁的类
- 用聚合关系描述复杂类
- 细化对象的分类
7.4 按编程语言调整继承
- 多继承调整为单继承
(1)采用聚合的方式代替继承
(2)提高多继承的类的层级 - 取消继承
- 取消多态性
7.5 转化复杂关联并决定关联的实现方式
- 转化复杂关联
(1)把关联类和 n 元关联转化为二元关联
(2)把多对多关联转化为一对多关联 - 决定关联的实现方式
(1)聚合
(2)关联
7.6 调整与完善属性
按照语法:[ 可见性 ] 属性名 [ ‘:’ 类型 ] [ ‘=’ 初始值 ] 对属性的定义进行完善。
可见性包括:私有——‘-’、保护——‘#’、公有——‘+’、······
类型包括:整型——‘int’、浮点型——‘float’、字符串——‘String’、······
7.7 思考题
7.7.1 软件复用可以采用类的继承方式和类的聚合方式,比较两者的优缺点。
答:
聚合复用优点:封装性好、耦合性低、满足单一职责原则
缺点:系统复杂性提高
类继承复用优点:容易添加新的实现,容易进行修改和扩展类
缺点:破坏了封装性,耦合性高
7.7.2 简述怎样发现类之间的继承关系。
答:
为候选的类有可能和它的父类、子类在谈话中同时被发现。系统分析员意识到某个类的属性和操作也许能被运用到其他多个类当中去。另一种可能的情况是系统分析员注意到两个或者多个类可能具有相同的属性和操作数.
7.7.3 简述类继承和接口继承的区别,我们应该尽量使用哪一种?
答:
类继承根据一个对象的实现定义了另一个对象的实现。简而言之,它是代码和表示的共享机制。然而,接口继承描述了一个对象什么时候能被用來替代另一个对象。
类继承是派生中的类将继承父类的所有属性和方法,并且可以在派生类里添加自己的属性和方法,而接口继承则是在接口里只定义接口的方法,没有属性,并且方法不能实现,只有在派生他的类才实现该方法。类继承是编译的时候新建对象, 而接口实例是在运行时刻创建对彖。我们应该尽量使用接口继承,类继承会产生类爆炸现象
第8章 人机交互部分设计
8.1 什么是人机交互
人机交互部分是OOD模型的组成部分之一,突出人如何命令系统以及系统如何向用户提交信息。设计人机交互就是要设计输入和输出。
8.2 如何分析人机交互系统
- 分析与系统交互的人员参与者
(1)列举所有的人员参与者
(2)调查研究
(3)区分人员类型
(4)估计各类人员的比例
(5)了解使用者的主观需求
(6)按照一定的准则进行折中和均衡 - 从用况分析人员交互
(1)用况的构成:
参与者的行为和系统行为按时间顺序交替出现,左右分明。形成交叉排列的段落。
每个段落至少含有一个输入语句或输出语句;有若干纯属参与者自身或系统自身的行为陈述;可能包含一些控制语句或括号。
(2)抽取方法:
删除所有与输入、输出无关的语句
删除不再包含任何内容的控制语句与括号剩下的就是对一项功能的人机交互描述
8.3 如何设计人机交互部分
- 设计输入输出
(1)输入的细化
输入步骤的细化
输入设备的选择
输入信息表现形式的选择
(2)输出的细化
输出步骤的细化
输出设备的选择
输出信息表现形式的选择 - 分析处理异常事件的人机交互
- 命令的组织
(1)基本概念
基本命令:使用一项独立的系统功能的命令。
命令步:基本命令交互过程中所包含的具体输入步骤。
高层命令:由其他若干命令组合而成,起组织和引导作用
(2)命令的组织措施——分解与组合
分解:将一条含有许多参数和选项的命令分解为若干命令步
组合:将基本命令组织成高层命令,从高层命令引向基本命令
(3)结构图
8.4 人机交互部分的设计准则
- 使用简便
- 一致性
- 启发性
- 减少人脑记忆的负担
- 减少重复的输入
- 容错性
- 及时反馈
- 其它:艺术性、趣味性、风格、视感…
8.5 思考题
8.5.1 人机交互部分的组成部分。
答:
设计用户界面模型、该模型中的类、对象和提供实现人机交互操作的接口函数。
第9章 控制驱动部分设计
9.1 什么是控制驱动部分
控制驱动部分是OOD模型的外围组成部分之一,由系统中全体主动类构成。这些主动类描述了整个系统中所有的主动对象,每个主动对象是系统中一个控制流的驱动者。
9.2 控制流
控制流(control flow)———进程( process)和线程( thread)的总称,有多个控制流并发执行的系统称作并发系统(多任务系统)
9.3 如何设计控制驱动部分
- 识别控制流
(1)主动类的每个对象都代表一个控制流
(2)每一个分布站点上至少有一个控制流
(3)多任务同时进行,每个任务就是一个控制流
(4)为方便实现设立的控制流,如:处理机之间通讯的控制流
(5)每个异常事件都是一个控制流 - 审查
(1)控制流需要满足上面所举的五点
(2)保证控制流是高内聚、低耦合的
(3)如果协调代价太大,可以降低系统并发度 - 定义控制流
(1)控制流的描述和说明
(2)控制流的表示法
9.4 思考题
9.4.1 进程和线程的区别?
答:进程是程序执行过程中的动态映像,是操作系统进行资源分配和保护的基本单位。线程是进程中的一条执行流程,同一进程中的线程共享进程的资源,线程是操作系统进行处理机调度的基本单位。
9.4.2 进程间和线程间的通信方式?
答:
进程:
- 通过信号量机制进行通信
- 通过共享内存进行通信
- 通过邮箱和操作系统提供的发送和接收原语进行通信
- 通过管道进行通信
- 通过信号进行通信
- 通过套接字进行通信
线程:
- 锁机制
(1)互斥锁
(2)读写锁 - 信号量机制
- 信号机制
第10章 数据管理部分设计
10.1 什么是数据管理部分
数据接口部分是OOD模型中负贡与具体的数据管理系统衔接的外围组成部分,它为系统中需要长久存储的对象提供了在选定的数据管理系统中进行数据存储与恢复的功能。
大部分实用的系统都要处理数据的永久存储问题,数据保存于永久性存储介质,在数据管理系统的支持下实现其存储、检索和维护。在面向对象的系统中,数据的存储表现为对象存储
10.2 数据库和数据库管理系统
数据库:数据库是长期存在计算机内、有组织、可共享的数据集合。
数据管理系统:实现数据存储、检索、管理与维护的系统,包括文件系统和数据库管理系统两大类(文件系统file system、关系型数据库管理系统RDBMS、面向对象的数据库管理系统OODBMS)
数据库管理系统(DBMS) :用于建立、使用和维护数据库的软件。它对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。
10.2.1 文件系统
通常是操作系统的一部分,管理外存空间的文件数据,提供存储、检索、更新、共享和保护等功能。
文件结构
- 物理结构——文件数据在存储空间的存放方法和组织关系
- 逻辑结构——呈现给用户的文件结构,如流式结构、记录式结构等
文件系统提供的支持
- 在人机界面上进行操作的系统命令
- 在程序中使用的广义指令
创建、删除、打开、关闭、读、写、控制等
编程语言可以提供更方便的文件定义与使用方式
优点:
- 廉价,容易学习和掌握,对数据类型没有限制
缺点:
- 功能贫乏、低级
- 不容易体现数据之间的关系
- 只能按地址或者记录读写,
- 不能按属性进行数据检索与更新
- 缺少数据完整性支持
- 数据共享支持薄弱
10.2.2 关系型数据库管理系统
- 关系模型
给定一组域D1,D2,…, Dn,其笛卡尔积D1 × D2 × … × Dn的一个子集就是一个关系,又称二维表 - 基本要求:关系的每个属性必须是原子的
- 数据的组织:用二维表组织各类数据,既可存放描述实体自身特征的数据,也可存放描述实体之间联系的数据,每一列称作一个属性,每一行称作一个元组
- 数据的运算:提供并、交、差等集合运算以及选取、投影、联结等操作
- 范式
(1)第一范式(1NF):关系(表)的每个属性都应该是原子的,既不能属性还有属性。
(2)第二范式(2NF):关系中的非关键字都只能依赖关键字
(3)第三范式(3NF):满足第二范式的基础上没有传递依赖,即不能A依赖B,而B又依赖C,这样的话A就传递依赖C。
(4)Boyce-Codd范式(BCNF):关系中的决定因素都是候选关键字。
(5)第四范式(4NF):在BC范式的基础上没有多值依赖。
(6)第五范式(5NF):在第四范式的基础上没有连接依赖。
例如:不满足2NF的关系,规范化满足3NF。
10.2.3 面向对象数据库
- 介绍:面向对象的数据库系统是OO设计和编程的之间扩展,是为了存储对象并与面向对象程序设计语言交互而专门设计的。他是按对象存储数据的数据库管理系统。
- 特征:
(1)是面向对象的,应支持对象、类、操作、继承、聚合、关联等面向对象的概念。
(2)另一方面,他应具有数据库系统应具有的功能和特征。
10.3 如何设计数据管理部分(文件系统)
10.3.1 对象在内存空间和文件空间的映像
如何看待用文件系统存储对象
从应用系统的对象到文件记录的不同映射方式
10.3.2 对象在文件中的存放策略
1)基本策略:
把由每个类直接定义、需要永久存储的全部对象实例存放在一个文件中;每个对象实例的全部属性作为一个存储单元,占用该文件的一个记录。
2)提高检索效率——在对象和文件记录之间建立有规律的映射关系
- 对象名或关键字呈线性规律
- 按对象名或关键字的顺序形成文件记录
- 给出对象名称或关键字,快速地计算出它的存放位置
- 对象名称或关键字可以比较和排序
- 按关键字顺序安排记录,检索时采用折半查找法
- 建立按对象名称或者按关键字排序的索引表,通过该表中的记录指针找到相应的记录
- 其他措施
如散列表、倒排表、二叉排序树等等
10.3.3 设计数据接口部分的对象类
10.3.4 问题域部分的修改
增加一个一般类来定义它们,作为共同协议,供所有的永久对象类继承,每个永久对象类都要增加请求存储和恢复所需的属性和操作,以便向数据接口部分发出请求。
10.3 如何设计数据管理部分(关系型数据库)
10.3.1 对持久类的存储设计
- 确定关键字
- 对象数据的规范化:关系数据库要求存入其中的数据符合一定的规范,并且用范式衡量规范化程度的高低。未必规范化程度越高越好,规范化的代价———响系统的可理解性,增加了多表查询和连接操作。
规范化的两种策略
保持类图,对表规范化,缺点是对象的存储与恢复必须经过数据格式的转换
修改类图,对问题域的映射可能不像规范化之前那么直接。但是这个问题并不严重——利大于弊
10.3.2 对关系的存储设计
- 对关联的存储设计
(1)一对一:在关联连接线一端的类中定义一个(或一组)属性,表明另一端类的哪个对象实例与本端的对象实例相关联,该属性(属性组)应该和另一端的关键字相同,如果另一端的关键字包含多个属性,本端也要定义同样的多个属性在对应的数据库表中,一个表以该属性(或属性组)作为外键,另一个表以它作为主键,使前者的元组通过其属性值指向后者的元组。
(2)一对多:从多重性约束为“m”的一端指向多重性约束为“1”的一端。
映射为数据库表后,A表以B表的主键作为自己的外键。
(3)多对多:转化为两个一对多的关联
- 对聚合/组合的存储设计
(1)聚合:整体对象类和部分对象类分别建立一个表通过外键表现整体部分关系
(2)组合:把部分对象类的属性合并到整体对象类中
- 对继承的存储设计
(1)把子类属性集中到父类中,创建一个表
(2)为父类和子类都建表,父类与子类要用同样的关键字
(3)如果父类为抽象类,则把父类的属性放到子类中,为子类建表。 - 数据接口部分类的设计
设计一个名为“对象存取器”的对象类,它提供两种操作:
“对象保存”——将内存中一个对象保存到相应的数据库表中
“对象恢复”——从数据库表中找到对象所对应的元组,把它恢复成内存中的对象
(1)对每个要求保存和恢复的对象类,分别设计一个“对象保存”操作和一个“对象恢复”操作。
优点:每个操作都很容易实现,通常只需要一个数据操纵语句,(例如静态SQL语句)
缺点:操作个数太多,很难在问题部分采用统一的消息协议
(2)只设计一个“对象保存”操作和一个“对象恢复”操作供全系统所有要求保存和恢复的对象类共同使用。
优点:操作少,消息协议统一
缺点:实现难度大——表的名称、关键字的构成、对象的类型不能在编程时确定,需要编程语言和数据操纵语言提供较强的支持
面向对象分析与设计 - 问题域部分的修改
采用第一种方案时,问题域部分每个请求保存或恢复的类,都要使用不同的操作请求语句,这些请求只能分散到各个类中
采用第二种方案时,在问题域部分设计一个高层的类,提供统一的协议,供各个需要在数据库中存储其对象实例的类继承可以做到和采用文件系统时的处理完全一致
10.3 如何设计数据管理部分(面向对象型数据库)
从应用系统到数据库,从内存空间到外存空间,数据模型都是一致的。因此,几乎不要为此再做更多的设计工作。
类图中的类一般不需要类似于规范化的改造,也不需要专门设计专门负责对象保存与恢复的对象类
主要考虑:如何用OODBMS提供的数据定义语言、数据操纵语言和其它编程语言来实现OOD模型——实现类和对象的定义和对数据库的访问,必要时要根据语言的功能限制对类图做适当的修改。
10.4 思考题
10.4.1 数据库系统、数据库和数据库管理系统的区别是什么?
答:
数据库系统(DBS)包括数据库(DB)和数据库管理系统(DBMS),数据库是存储在计算机内、有组织的、可共享的数据和数据对象的集合,这种集合按照一定的数据模型组织、描述和长期存储,同时能以安全和可靠的方法进行数据的检索和存储。数据库管理系统是数据库系统的核心软件。数据库管理系统可以借助操作系统完成对硬件的访问,并能对数据库的数据进行存取、维护和管理。
第11章 构件及部署部分设计
11.1 构件设计
11.1.1 概念
- 构件:构件是系统中可替换的模块化部分,他封装了自己的内容;构件利用提供接口和请求接口定义自身的行为;他起类型的作用。
构件的含义:构件是系统的模块,封装了内部构成;构件通过它的供接口和需接口展现行为;构件是可替换的单元,在设计时和运行时依据接口的兼容性,如果一个接口可以提供另一个接口具有的所有功能,则可以进行替换;构件是可复用的单元,只要应用它的环境需要它的供接口且可以满足它的需接口,则可以把它应用在其中;构件是可组装的,把它们的请求和提供接口连接在一起,可形成粒度更大的构件;构件起类型作用,构件可以实例化。 - 构件的接口
(1)接口:接口由一组操作组成,刻画模型元素对外提供和自身所需要的服务。
(2)供接口:构件的共接口是构件实现的接口,提供接口给其他构件提供服务。
(3)需接口:构件的需接口是构件需要使用的接口,即构件向其他构件请求服务时要遵循的接口。 - 构件的端口
(1)构件的端口:接口对声明一个构件的总的行为来说是有用的,构件的实现仅需要保证实现在提供接口中的全部操作。使用端口是为了能进一步控制这一种实现。
(2)端口的作用:端口描述了构件与它的环境之间以及构件与它的内部部件之间的一个显示交互点;一个构件可以通过一个特定端口同另一个构件通讯;可以按端口把构件的接口分组,并独立使用。构件内部的部件用端口名来识别要通过那个端口来接收和发送消息。 - 连接件
(1)连接件:连接端口意味着请求端口要调用提供端口中的操作,以得到服务。连接件就是通过端口或接口用于构件实例间通讯的部件。
(2)连接件的种类:1)装配连接件:用于把一个请求接口或端口与另一个提供端口或端口连接起来。2)委托连接件:委托连接件把外部对构件端口的请求分发到构件内部的部件实例进行处理,或者通过构件端口把构件内部部件实例向构件外部的请求分发出去。
11.1.2 构件的内部结构
- 构件的组成:构件由实现它的一个或者多个部件组成,把这些部件以及其之间的关系称之为它的内部结构。
- 构件的内部结构表示法
(1)使用依赖关系
(2)在构件图的内部展示
(3)分栏展示
11.1.3 对构件的行为建模
- 可以在接口、端口上和构件本身上附属诸如协议状态机这样的行为;
- 可以通过按调用操作的顺序显式地给出动态约束,以较精确地定义外部行为
- 可以把其他的行为(如用况、活动或者交互规约)与接口或者连接件相关联,以定义协作间的契约。
11.1.4 对构件的实现建模
- 制品:开发出来的具体物理产品。
- 制品的分类:1)工作产品制品:主要是开发过程的产物,包括实现构件的源代码文件以数据文件,但不直接地参加系统地执行。2)可执行制品:用于构造可执行地系统,如:动态链接库(DLL)、可执行程序(exe)
- 制品的表示:画成带有关键字<<artifact>>的矩形。
- 对构件的实现建模的情形:
(1)对构件的源代码建模:识别出一组相关源代码文件集合,考虑设置一个标记,用它给出源代码文件的版本号、作者名等等信息,最后用依赖关系对这些文件之间的编译依赖关系建模。
(2)对构件的实现制品建模:决定构件由哪些制品实现,决定可执行制品之间的关系。
(3)对构件及实现构件的部署制品建模:使用分栏结构表示构件及实现构件的部署制品,通过实现关系也可以把实现构件的制品和构件关联起来。
11.2 部署设计
11.2.1 概念
- 节点:节点是制品可部署并执行在其上的计算资源,并能够通过通信路径互联。
- 节点表示:用立方体表示
- 节点关系:1)关联。2)用包组织节点。
11.2.2 对系统的部署建模
- 对嵌入式系统建模
嵌入式系统是软件和硬件的协作体,其硬件与物理设备连接,软件包括控制设备的软件和由传感器控制的软件等。
用部署图对组成嵌入式系统的处理器、设备以及构件在其上的分布情况建模。 - 对分布式系统建模
将在不同地点、具有不同功能或拥有不同数据的多个节点用通讯网络连接起来的,在控制系统的统一管理控制下,协作完成信息处理任务的系统。
这样的系统要求各节点之间用网络连接,系统中的构件要物理地分布在节点上。用部署图描述系统地网络拓扑结构以及在构件在其上的分布情况。
11.3 思考题
11.3.1 构件图的作用。
答:
使系统人员和开发人员能够从整体上了解系统的所有物理部件,同时,也使我们知道如何对构件进行打包,以便交付给最终客户,最后构件图显示了被开发系统所包含的构件之间的依赖关系。
使用构件图可以清楚地看出系统的结构和功能。方便项目组的成员制定工作目标和了解工作情况,最重要的是使用构件图有利于软件的复用。
第12章 若干典型的设计模式
12.1 设计模式原则
设计原则名称 | 设计原则简介 | 重要性 |
---|---|---|
单一职责原则 | 类的职责要单一,不能将太多的职责放在一个类中 | ★★★★☆ |
开闭原则 | 软件实体对扩展是开放的,但对修改是关闭的,即在不修改一个软件实体的基础上去扩展其功能 | ★★★★★ |
里氏代换原则 | 在软件系统中,一个可以接受基类对象的地方必然可以接受一个子类对象 | ★★★★☆ |
依赖倒转原则 | 要针对抽象层编程,而不要针对具体类编程 | ★★★★★ |
接口隔离原则 | 使用多个专门的接口来取代一个统一的接口 | ★★☆☆☆ |
合成复用原则 | 在系统中应该尽量多使用组合和聚合关联关系,尽量少使用甚至不使用继承关系 | ★★★★☆ |
迪米特法则 | 一个软件实体对其他实体的引用越少越好,或者说如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,而是通过引入一个第三者发生间接交互 | ★★★☆☆ |
12.2 外规模式
引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。
外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式又称为门面模式,它是一种对象结构型模式。
外观模式由三个角色组成:
外观角色:外观模式的核心。它被客户角色调用,因此它熟悉子系统的功能。其内部根据客户角色已有的需求预定了几种功能组合。
子系统角色:实现子系统的功能,对它而言,外观角色就和客户角色一样是未知的,它没有任何外观角色的信息和链接。
客户角色:调用外观角色来完成要得到的功能
根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口。
外观模式也是“迪米特法则”的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度。
在外观模式中,通常只需要一个外观类,并且此外观类只有一个实例,换言之它是一个单例类。
在很多情况下为了节约系统资源,一般将外观类设计为单例类**。**当然这并不意味着在整个系统里只能有一个外观类,在一个系统中可以设计多个外观类,每个外观类都负责和一些特定的子系统交互,向用户提供相应的业务功能。
不要通过继承一个外观类在子系统中加入新的行为,这种做法是错误的。
外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,
抽象外观类的引入
外观模式最大的缺点在于违背了“开闭原则”,当增加新的子系统或者移除子系统时需要修改外观类,可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类进行编程。
对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的。
12.3 适配器模式
在软件开发中,为了解决接口不一致的问题,两个软件模块之间往往也需要通过一个适配器类Adapter进行“适配”。这样的模式叫做适配器设计模式。
通常情况下,客户端可以通过目标类的接口访问它所提供的服务。
有时,现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现有类中方法名与目标类中定义的方法名不一致等原因所导致的。
在这种情况下,现有的接口需要转化为客户类期望的接口,这样保证了对现有类的重用。如果不进行这样的转化,客户类就不能利用现有类所提供的功能,适配器模式可以完成这样的转化。
适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。
也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。
因此,适配器可以使由于接口不兼容而不能交互的类可以一起工作。
该模式可以分为两种,分别为类适配器模式(Class Adapter Pattern)和对象适配器模式(Object Adapter Pattern)。
示例:
某公司购买了一个用于验证客户信息的离架产品类InfoValidation,但是卖方没有提供源代码。该类可以用于检查客户输入的信息,包含验证姓名、地址、电话区号和手机号码等功能。如果还需要增加一个验证社会安全号(SSN)的功能,则可以使用类适配器模式来实现。
解析:
示例:
使用对象适配器实现字符串序列排序。要求从一个.txt文件读入一些英文字符串,并且对这些字符串进行排序。
这里已有一个类FileInput,其主要功能包含从一个文件中读入字符串;另外,在一个Java类库中有一个Arrays,其中包含功能sort,用于对多个字符串进行排序。
解析:
类适配器模式与对象适配器模式的区别:
在Java语言中,使用对象适配器模式可以把多种不同的源类都适配到同一个目标接口,而使用类的适配器模式是做不到这一点的。
如果一个被适配源类中有大量的方法,使用类适配器模式比较容易,只需要让Adapter类继承被适配的源类即可。而此时使用对象适配器模式则要在Adapter类中明确写出Target角色中的每个方法,并且在每个方法中要调用被适配的源类中的相应的方法。
优点:
将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
由于类适配器是适配者类的子类,因此可以在类适配器中置换一些适配者的方法,使得适配器的灵活性更强。
一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
缺点:
对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
适用场合:
- 系统需要使用现有的类,而这些类的接口不符合系统的需要。
- 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
适配器模式的扩展:
- 默认适配器模式(Default Adapter Pattern)或缺省适配器模式
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。因此也称为单接口适配器模式。
- 双向适配器
在对象适配器的使用过程中,如果在适配器中同时包含对目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法,那么该适配器就是一个双向适配器。
12.4 策略模式
策略模式定义了一系列算法,将每一个算法封装起来,并且使它们之间可以相互替换。策略模式让算法的变化不会影响到使用算法的客户。
策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
- Strategy:定义了一个共同的接口,所有具体的算法类实现这个接口。环境(上下文)类Context使用这个接口调用具体的算法类。
- ConcreteStrategy:封装了具体的算法,实现同一个接口。
- Context:环境(上下文)类。用于配置一个具体的算法策略对象,维持一个策略接口类型的参考(Reference),并且可以定义一些让接口Strategy的具体对象访问的接口。在简单情况下,Context类可以省略。
优点:
- 得到一系列可以复用的算法,这些算法继承一个共同的抽象类,因此共有的功能可以放到超类中;
- 将不同算法封装在不同的策略子类中,使逻辑更加清晰,各个算法可以独立地变化;
- 使功能改变或者扩展更容易,修改一个算法不必重新编译“Client”与“Context”。
缺点:
- 客户程序必须知道不同策略接口的各个子类的行为,必须理解每个子类有哪些不同。因此,在客户类中通常存在许多与策略类各个分支相关的条件语句,用于选择产生策略子类对象,然后将这些对象传递给Context类,而Context类则直接使用此对象调用策略模式的策略子类的方法。
12.5 观察者模式
这种模式称为“被观察者/观察者”,每一个模块都允许其他模块向自己所发送的某些消息表明兴趣。当某一模块发出某一事件时,它自动将这些事件发布给那些曾经向自己注册过此事件的模块。
观察者模式的各组成部分说明如下:
- Observable:被观察者接口,声明了三个应该实现的方法。在简单的情况下,register()方法负责将参数中的观察者注册到Subject对象,在Subject对象中保持一个具体的观察者列表,用于记载所有的观察者。unRegister()方法用于在列表中删除参数中的观察者对象。notify()方法用于通知观察者subject状态的改变。
- Subject:具体的观察者要依赖的对象,它要实现Observable的所有方法。在Subject中的getState()方法可以被ConcreteObserver调用,以便得到最新的状态。
- Observer:观察者接口,代表依赖对象。观察者可以有多个。
- ConcreteObserver:代表具体的观察者对象。
观察者模式的工作原理如下:
- 被观察者保持一个数据结构,如Java ArrayList,用于记载动态添加的观察者。
- 对被观察者状态感兴趣的对象(观察者),应该调用被观察者的Register方法将自己注册为它的一个观察者。
- 每当被观察者的状态发生改变的时候,它将使用方法notify()通知已经注册的观察者。
- 当接到通知以后,每个观察者都将查询被观察者的状态,以便保持状态同步。根据新的状态,观察者将决定做一些图标更新或者其他相关的操作。
- 观察者将提供一个接收被观察者通知的接口,例如synchronizeState(),被观察者在方法notify()中可以调用该接口。
12.6 抽象工厂模式
在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。
产品等级结构:——同一产品类型,不同工厂生产
产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL电视机,则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
产品族:——同一工厂生产,不同产品类型
在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱,海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。
抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
示例:
一个电器工厂可以产生多种类型的电器,如海尔工厂可以生产海尔电视机、海尔空调等,TCL工厂可以生产TCL电视机、TCL空调等,相同品牌的电器构成一个产品族,而相同类型的电器构成了一个产品等级结构。
请使用抽象工厂模式模拟该场景。
解析:
抽象工厂模式的可扩展性:
增加新的工厂符合开闭原则,增加新类型的产品不符合开闭原则。
优点:
抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。
所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用。
当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。
增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
缺点:
在添加新的产品对象时,难以扩展抽象工厂来生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便。
开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)
适用场合:
一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
系统中有多于一个的产品族,而每次只使用其中某一产品族。
属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
抽象工厂模式包含四个角色:
- 抽象工厂用于声明生成抽象产品的方法;
- 具体工厂实现了抽象工厂声明的生成抽象产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中;
- 抽象产品为每种产品声明接口,在抽象产品中定义了产品的抽象业务方法;
- 具体产品定义具体工厂生产的具体产品对象,实现抽象产品接口中定义的业务方法。
12.7 工厂方法模式
在简单工厂模式中,只提供了一个工厂类,该工厂类处于对产品类进行实例化的中心位置,它知道每一个产品对象的创建细节,并决定何时实例化哪一个产品类。
简单工厂模式最大的缺点是当有新产品要加入到系统中时,必须修改工厂类,加入必要的处理逻辑,这违背了“开闭原则”。
在简单工厂模式中,所有的产品都是由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间的耦合度高,严重影响了系统的灵活性和扩展性。
为克服简单工厂方法模式的不足,人们试图改善工厂类结构以解决这一问题。
软件设计者们发现,可以将简单工厂方法模式中单一的工厂类改写为一个层次类来解决这个问题。
- 首先需要一个接口作为超类,名为factory,接口中有一个方法,叫做create();
- 然后可以用和产品类相同的结构产生创建者类结构,其中包含factoryA和factoryB,各自负责创建相应的ProductA和ProductB的对象。
工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。
在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这种细节,
这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。
当系统扩展需要添加新的产品对象时,仅仅需要添加一个具体产品对象以及一个具体工厂对象,原有工厂对象不需要进行任何修改,也不需要修改客户端,很好地符合了“开闭原则”。而简单工厂模式在添加新产品对象后不得不修改工厂方法,扩展性不好。
工厂方法模式退化后可以演变成简单工厂模式。
优点:
在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。
缺点:
在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
适用场景
一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
12.8 思考题
12.8.1 抽象工厂模式案例
使用工厂方法模式设计一个系统日志记录器,该系统日志记录器要求支持多种日志记录方式,如文件日志记录(FileLog)、数据库日志记录(DatabaseLog)等,每种记录方式均具有writeLog()方法记录数据,客户端(Client)可选择日志记录方式(logType)通过调用工厂类(LogFactory)中的createLog()方法创建日志(Log)对象,
请按要求画出类图及其关键属性和操作。
解析:
12.8.2 适配器模式案例
某公司欲开发一款儿童玩具汽车(Car),为了更好地吸引小朋友的注意力,该玩具汽车在移动(move)过程中伴随着灯光闪烁(twinkle)和声音提示(sound),在该公司以往的产品(OldProduct)中已经实现了控制灯光闪烁和声音提示的程序,为了重用先前的代码并且使得汽车控制软件具有更好的灵活性和扩展性,使用适配器(CarAdapter)模式设计该系统,
请按要求画出类图及其关键操作。
解析:
12.8.3 外观模式案例
在金融机构中,当有客户(Client)前来抵押贷款mortgage()时,需通过抵押系统(Mortgage)对客户进行合格性验证isQualified(),只有验证通过后才能进行抵押贷款。
抵押系统的合格性验证需要三个子系统同时工作:身份验证(Authentication)子系统确认客户身份是否合法isLegal()、信用(Credit)子系统查询其信用是否良好isCredible()以及贷款(Loan)子系统查询客户是否有贷款劣迹hasBadLoans()。只有这三个子系统都通过时才能通过合格性验证。
使用外观模式模拟该过程,请按要求画出类图及其关键属性和操作。
解析:
12.8.4 策略模式案例
现有一种玩具,它可以模拟三种动物(Animal)的叫声:狗叫(Dog)、猫叫(Cat)和青蛙叫(Frog),每一种动物都有cry()方法用于实现发出叫声的操作。玩具类(Toy) 使用setAnimal()方法设置动物,并使用属性currentAnimal记录玩具当前模拟的动物,使用 run()方法来调用动物的cry()方法使玩具发出叫声。为将来能模拟更多的动物,采用策略模式设计该系统,请按要求画出类图及其关键属性和操作。
解析:
12.8.5 观察者模式案例
模拟锅炉的温度显示情况。假如被观察者代表锅炉,用TemperatureGUI表示。为了以不同的方法显示温度值,设计了几个观察者,摄氏温度图形显示界面CelsiusGUI对象、华氏温度图形显示界面FahrenheitGUI对象和凯文温度显示界面KelvinGUI对象,共同观察被观察者TemperatureGUI对象。
当用户在TemperatureGUI图形界面中输入摄氏、华氏或者凯文温度的时候,相应地,根据温度换算的情况,几个温度观察者界面将分别显示摄氏、华氏或者凯文温度,并且还显示温度颜色,即用不同的颜色表示不同的温度。使用观察者模式,进行设计。
解析:
第13章 OOD的评价准则
13.1 耦合
- 耦合:描述OOD成分之间联系的紧密程度
- 考虑耦合的目的:改动一部分,对其他部分影响最小;阅读一部分,查阅其他部分较小
- 耦合种类:
(1)交互耦合:消息的交互程度。越低越好
(2)继承耦合:特殊类继承自一般类的属性和操作的数量程度。越高越好。
13.2 内聚
- 操作内聚
(1)高内聚:一个操作完成一个功能
(2)低内聚:一个操作实现多个功能,或者只实现部分功能的一部分。判断:操作的方法中若分支语句过多,嵌套层次过深,操作的大小,用简单的句子描述。 - 类内聚:指属性和操作应该是高内聚的,没有多余的属性和操作,都是描述对象本身的责任。
- 继承内聚:满足继承关系讲的通;特殊类真正描述了一般类的特化,确实继承了一般类的许多属性和操作。
13.3 复用
- 复用意义:因为可复用的软件制品都应该是已经经过实际检验的,复用已有的软件制品,可以节省成本,提高质量。
- 复用级别:对可执行代码、源代码复用,对分析结果、设计结果或框进行复用,通过继承对一些建模元素的复用。
13.4 其他评价准则
- 清晰度要好
(1)使用一致的词汇表
(2)遵循已有的协议
(3)消息模板要少
(4)明确定义类的职责
(5)把策略方法和实现方法分开 - 继承的层次要适当
- 保持对象和类的简单性
(1)避免太多的属性
(2)对象之间的协作最小化
(3)避免一个对象中有太多的可见性为公共的操作
(4)保持操作的简单性 - 适当使用所有需要的属性和方法
- 尽量使用与提炼设计模式
- 考虑设计易变性的最小化
13.5 思考题
13.5.1 高内聚低耦合表明的是什么设计思路?
答:
高内聚低耦合是软件工程中的概念,是判断设计的好坏的标准,主要是面向对象设计,主要是看类的内聚性是否高,耦合度是否低。
13.5.2 什么是高内聚度?
答:
高内聚度是对一个类中的各个职责之间相关程度和集中程度的度量。一个具有高度相关职责的类并且这个类所完成的工作量不是特别巨大,那么它就具有高内聚度。包括两个意思:不要给一个类分派太多的职责,在履行职责时尽量将部分职责分派给有能力完成的其他类去做。不相关的职责不要分派给同一个类。
第四部分 系统与模型
第14章 系统与模型
14.1 系统与子系统
系统:系统是由为了达到特定目的的而组织起来的模型元素构成,可用从不同抽象层次和不同角度建造的模型来描述它。
子系统:如果一个系统比较复杂,可把它分解成一组子系统,每个子系统要完成特定的功能,有自己的应用环境,通过接口与系统的其他子系统交互。
划分子系统:对一个大而复杂的系统,要把它分解成若干较小的子系统,在对每一个子系统进行求解,最后把这些子系统集成为系统。
分解系统考虑的因素:
- 系统功能
- 可使用的体系结构风格和模式
- 系统的网络拓扑结构和系统的软件分布情况
- 对系统使用的约束
- 与外系统结合的情况
- 系统的成本、灵活性和最优性等等
14.2 模型
14.2.1 模型的含义
- 模型:模型是为了更好地理解所要建造的系统,通过对现实世界的简化而构造的系统语义抽象。
- 使用模型的目的:1)从多方面理解系统;2)便于各类人员的交流与协作;3)创建完整的、经过验证的无二义的编程规格说明;3)有利于维护;4)协作项目的规划和管理;5)支持质量保证和验证活动。
14.2.2 模型与视图
- 视图:视图是系统模型在某一侧面的投影,即它在观察或突出所被建模的系统的一个侧面,忽略与这一侧面无关的其他方面。
- 视图的五个方面:设计视图、进程视图、实现视图、部署视图和用况视图
- 视图的构成:
状态 | 设计视图 | 进程视图 | 实现视图 | 部署视图 | 用况视图 |
---|---|---|---|---|---|
静态 | 类图、对象图、结构图 | 类图、对象图、结构图 | 构件图、组合结构图 | 部署图 | 用况图 |
动态 | 交互图、状态图、活动图 | 交互图、状态图、活动图(更加注重进程和线程) | 交互图、状态图、活动图 | 交互图、状态图、活动图 | 交互图、状态图、活动图 |
- 模型:无论从那个视图进行建模,都会产生系统的一个局部模型,该模型由相应种类的图构成。
14.2.3 模型的抽象层次
- 需求捕获阶段:建立的模型要描述系统的需求,此阶段应该用用况图描述用况模型,如果需要,还可以选用活动图等进一步地描述用况模型。
- 分析阶段:结合问题域和系统责任,以用况模型为基础,对系统建模。用类图等描述模型元素以及其间的关系,强调系统的逻辑结构和系统的组织;用交互图、状态图或活动图描述系统的行为方面,强调系统中模型元素间得到动态联系。
- 设计阶段:以分析阶段的结果作为基础,进行问题域设计,但重点考虑和实现有关的因素。此外还要进行人机交互、控制流、数据管理和系统的实现与部署等方面的设计。
14.2.4 模型间的一致性检查
- 描述相同事物的模型之间的检查
(1)在不同抽象层次上检查描述相同事物的模型的一致性
(2)同一模型在不同侧面上的一致性检查 - 不同模型之间的检查
(1)全局检查
(2)动态检查检测策略:1)工具中编辑操作一般都有附加强制条件,若在建模时不满足条件,就不能继续进行。2)工具检查到错误,给出警告,但允许继续进行建模。3)当工具检测到已发生的不一致时,要确定这种不一致所涉及的范围,并自动地进行一定程度上的修正。
14.3 思考题
14.3.1 说明视图、模型及系统之间的关系。
答:
对客观现实的事物进行抽象和刻画所得到的结果,称为模型。将部分模型数据以可视化、便于理解的形式呈现出来就是视图。模型是为了更好地理解所要建造的系统,通过对现实世界的简化而构造的系统语义抽象。