面向对象的分析与设计
Unified Process Elaboration - 2
Xiao ding TSEG
细化阶段:迭代 2
设计模型
领域模型描述了概念类、属性、关联
用例模型表达了系统功能需求、系统事件和系统操作契约。
那么谁来完成系统的功能呢?
概念类转化到设计类(包括属性和关联)
设计类的职责是什么?
设计类如何协作处理系统事件、满足系统操作契约,并最终实现系统功能来满足用例的需求?
其中设计对象的职责非常关键。
GRASP( General Responsibility Assignment Software Patterns)
是一种软件设计模式,称为对象职责分配模式。
细化阶段:迭代 2
GRASP 基于职责设计对象
职责和方法:职责是抽象,方法实现了职责
对象的职责分为两种类型:
了解型( knowing)
了解私有的封装数据;
了解相关联的对象;
了解能够派生或者计算的事物。
行为型( doing)
自身执行一些行为,如创建一个对象或者进行计算;
启动其他对象中的动作;
控制或协调其他对象中活动。
方法是对象操作的实现,是完成对象职责的手段。职责既可以由一个方法实现,也可以与其他方法或者对象协作实现。
Sale具有了解其销售总额的职责
Sale具有创建 SaleLineItem对象的职责细化阶段:迭代 2
职责与顺序图
顺序图体现了如何为对象分配职责:一个对象接收了某条消息,表明该对象具备了处理该条消息的职责。
职责的分配来源于顺序图,也就是说当绘制顺序图的时候就是在决定职责的分配。
“POS” 机使用 makePayment
消息向“销售”发出请求,“销售”在相应的 makePayment方法中进行处理;完成这个职责还需要通过协作创建“付款”对象,
并调用其构造器
,POS 机,Sal e
,付款
mak ePa y men t (ca s hTe n der e d)
抽象,表示“销售”对象具有创建“付款”的职责
cre ate ( cas h Ten d ere d )
细化阶段:迭代 2
GRASP 设计模式
创建者 ( Creator)
信息专家 ( Information Expert)
控制器 ( Controller)
低耦合 ( Low Coupling)
高内聚 ( High Cohesion)
多态( Polymorphism)
纯虚构( Pure Fabrication)
间接性( Indirection)
防止变异( Protected Variations)
细化阶段:迭代 2
GRASP 创建者
问题来源:谁该负责创建某类的实例
解决方案:如果满足以下的条件之一(越多越好),则可将创建类 A
的职责分配给类 B。
B聚合( aggregate)对象 A;
B包含( contain)对象 A;
B记录( record)对象 A的多个实例;
B密切使用对象 A;
B拥有创建对象 A所需要的初始化数据( B是创建对象 A的信息专家)
那么,B是对象 A的创建者;如果有多个选项满足的情况下,首选聚集或包含 A的类 B。
创建者模式的意图是寻找在任何情况下都与被创建者对象具有连接的创建者,且具有低耦合的特点。
案例问题:谁应当负责创建 SaleLineItem实例?
细化阶段:迭代 2
GRASP 创建者
在领域模型或用例说明中去寻找聚合、包含 SaleLineItem实例的类。
发现概念类 Sale包含多个 SaleLineItem对象,为此根据创建者模式,
Sale是具有创建 SaleLineItem实例职责的候选者,进而可产生以下顺序图
这个模式的优点在于支持低耦合
,POS 机,Sal e
,Sal e Lin e Ite m
mak eLi n eIt e m(q ua nti t y)
cre ate ( qua n tit y)
这个职责的分配要求在 Sale
中定义 MakeLineItem方法。
强调:在绘制顺序图时考虑和决定这些职责的分配。
细化阶段:迭代 2
GRASP 信息专家
问题来源:给对象分配职责的原则是什么?
解决方案,将职责分配给拥有履行一个职责所必需信息的类,即信息专家。换言之,对象处理自己拥有信息的事务。
案例问题,谁应该负责获取一次销售的总额?
在具体案例中,某个类需要知道一次销售的总额。即,谁应当了解销售的总额?
设计模型中还没有软件类,怎么办?
此时,,信息专家,就是领域模型。
查看领域模型,找出拥有相关信息的概念类,将其应用或扩展到设计模型,形成软件类。
按照信息专家原则,我们找到了概念类 Sale。
在设计模型中加入 Sale软件类,为其分配获取总额的职责并以名为
getTotal的方法来表示
Sal e
dat e
tim e
Sal eLi ne Ite m
qua nti ty
Ite m
Ite mID
Pro duc tD esc r ipt io n
Ite mID
des cri pt ion
pri ce
1.,n
1
Con tai ne d-i n
10.,1
Rec ord s- sal e -of
1
n
des cri be s
部分领域模型按照信息专家模式找出软件类 Sale的 getTotal操作销售总额=销售量 × 销售价格
SalesLineItem.quantity
ProductSpecification.price
注意:职责的实现需要分布在不同的对象中的信息,一个任务需要多个对象(信息专家)协作来完成。
为了实现获知和回答销售总额的职责,找到了三个对象并分配了三个职责。
Sale
date
time
get Tota l()
添加一个新方法get Tota l()
,S a l e,P O S 机 1,t = g e t T o t a l ( )
,POS 机,Sale
lin eItem s[i],
Sal eLine Item
,Produ ct
Des cript ion
1,1:t=g etTo tal()
2,2*[mo re i tems],st=g etSu bTota l()
3,2.1:g etPr ice()
Sa le
da te
ti me
ge tTo ta l( )
Sa leL in eI te m
qu ant it y
ge tSu bT ot al ()
Pr odu ct
De scr ip ti on
It emI D
de scr ip ti on
pr ice
ge tPr ic e( )
细化阶段:迭代 2
GRASP 信息专家的优点
优点:
维持了信息封装性,因为对象使用自己的信息完成任务。支持了低耦合,提高了系统的健壮性和可维护性。
系统行为分散在不同的类中,这些类提供处理自己信息的能力,
使得这些类易于理解和维护。
注意:
当一个类按照信息专家模式得到的职责有很多种类型的时候,就产生了类的内聚性问题。
需要利用隔离原则,将不同逻辑方面的职责进行隔离,分配给不同的软件对象,以提高内聚性。
相关模式:低耦合模式、高内聚模式细化阶段:迭代 2
GRASP 低耦合
问题来源:怎样降低依赖性,减少变化带来的影响,提高重用性?
解决方案,分配一个职责给对象时,要保持对象间的低耦合度。减少对象间的依赖性,从而减少变更带来的影响,提高对象的重用性。
耦合:测量一个元素连接、了解或者依赖其他元素的强弱尺度。
使用高耦合性的类会出现的问题
A
B
C
D
如果类 A和其他的类之间关系简单一些就好了!
修改 A,会影响 B,C,D?
想重用 A,太麻烦!
细化阶段:迭代 2
GRASP 低耦合 案例分析
考虑创建 Payment实例并使它与 Sale关联。
问题:哪个类应负责这件事情呢?
方案 1,现实世界中是 POS机记录了 Payment,根据创建者原则建议将 Pos机作为 Payment的候选者。 Pos机会把 addPayment消息发送给 Sale,并把新的 Payment作为参数传递给它。
,Ca sh ie r,PO S 机
1,Ma ke Pa ym e nt ()
,Pa ym en t
,Sa le
2,cr at e( )
3,ad dP ay me n t( p)
这种职责分配使得 Pos机与 Payment之间产生了耦合,
即 Pos机类必须知道 Payment类细化阶段:迭代 2
GRASP 低耦合 案例分析
方案 2,回顾领域模型中这三个类之间的关系,发现 Sale
与 Payment类之间有关联关系
考虑支持低耦合,由 Sale类来创建 Payment类的实例,
这样避免了 Pos机类与 Payment了之间的耦合。
,Cas h ier,POS 机
,Sal e,Pay m ent
1,Mak e Pay m ent ()
2,Mak e Pay m ent ()
3,cre a te( )
细化阶段:迭代 2
GRASP 耦合关系
在制定设计决策阶段必须考虑低耦合问题。
在 C++,Java这类面向对象语言中,两个类之间的耦合常见的形式包括:
X具有引用 Y实例或者 Y本身的属性;
X调用 Y对象中的服务;
X的方法中有 Y类型(参数或返回);
X是 Y的直接或间接子类;
Y是一个接口,X实现了这个接口。
耦合的权衡原则,
尽量降低耦合,但耦合是不可避免的,因为系统的任务是通过关联对象之间的协作完成的;
耦合一个高稳定的系统元素不是一个问题,因为不会发生变更;
更多地考虑与系统不稳定元素之间的耦合,尽量降低这种耦合。
细化阶段:迭代 2
GRASP 高内聚
问题来源:怎样保持对象是有重点的、可理解的、可管理的,并且能够支持低耦合?
解决方案:分配一个职责给对象时,要保持对象本身功能的高内聚度。
对象内聚度:是对象职责联系的紧密程度。
一个低内聚的对象会执行许多互不相干的功能,或需要完成大量的工作。这样的类是不合理的,会导致下列问题:
难于理解、难于重用、难于维护;
系统脆弱,常常受到变化的影响。
大粒度对象,承担了本该委托给其他对象完成的职责。
经验:一个具有高内聚的类具有数目相对较少的方法和紧密相关的功能,但是它并不完成太多的工作。任务过大时,寻求与其他对象协作完成。
细化阶段:迭代 2
GRASP 高内聚 案例分析
同样考虑创建 Payment实例,并与 Sale类相关联。
如果按照前述的方案 1,那么 Pos机将具有“支付”的职责,并完成 makePayment系统操作的部分职责。
如果 Pos机负责的职责越来越多且与系统操作有关的某些或大部分工作,它的任务负荷将不断增加,而成为非内聚的类。
这种方案对于系统实现来说并非不可取,但是需要从系统整体职责分配的角度出发,它可能具有低内聚的倾向。
如果采用方案 2,则将创建 Payment的职责分配给了 Sale
类,一定程度上支持了 Pos机的高内聚。
方案 2既支持了 Pos机类的高内聚也支持了它的低耦合性,
所以它是我们所需要的系统设计。
细化阶段:迭代 2
GRASP 控制器
问题来源:在系统边界之后( UI Layer之后)首先接收和协调系统操作的第一个对象是什么?
控制器是我们寻找的第一个对象,它负责接收和处理系统操作消息。
解决方案:把接收或者处理系统事件的职责分配给这样一个类:
1,它代表整个系统、设备或者子系统,称为外观控制器
2,它代表一个发生系统事件的用例场景,称为用例控制器,通常命名为,
<UseCaseName>Controller/Coordinator/Session
在相同的用例场景中使用同一个控制器类处理所有的系统事件;
一次会话是与一个角色进行交谈的一个实例。
注意:,window/view” 等类不参与完成与系统事件相关的任务,它们接收这些事件后,将其委派给控制器。
通常,UI层不应当包含应用逻辑,为此 UI层对象必须把外部系统事件的请求委派给其他层。如果“其他层”为领域层时,就需要确定相应的领域对象。
细化阶段:迭代 2
GRASP 控制器 示例哪个对象应负责接收这个系统事件?
有时称它为控制器或协调者。它通常不实现职责,而是将职责委托给其他对象控制器是从界面层到领域层的外观对象两种选择:外观控制器、
用例控制器细化阶段:迭代 2
GRASP 控制器使用原则
当一个系统不具有,太多,的系统事件,或者用户接口不可能将事件消息重定向到其他控制器时,选择外观控制器是合适的 。
这时,外观控制器相当于一个应用的封面,隔离了用户接口和应用逻辑。
如果在外观控制器中由于过多的职责而变得,臃肿,的时候,应该选择用例控制器 。
如果选择了用例控制器,那么每一个用例都有一个不同的控制类,
而且只有一个,以便维护用例的状态。
用例控制器可以实现有一定执行顺序的系统操作。
不论是外观控制器还是用例控制器,它们只是接收系统事件消息,并没有实现系统操作的职责,系统操作应该委托给领域对象处理 。
细化阶段:迭代 2
GRASP 系统操作的分配细化阶段:迭代 2
GRASP UI层到领域层的耦合关系细化阶段:迭代 2
UP 分析模型
分析模型可作为从用例模型及领域模型过渡到设计模型的一种手段。
分析模型由一些抽象地分析类构成,用以展示系统相关的抽象用例实现过程。
边界类,位于系统边界,包括所有窗体、报表、与硬件设备以及其他系统的接口
控制类,用于模拟一个或几个用例的特定行为,通常它控制和协调其他的对象而不进行具体的业务逻辑实现,即它封装了用例的特定行为
实体类,具有保存那些要持久存储的信息的方法,通常用领域模型的术语进行命名。数据库设计时可以对每个实体类生成一张表
仅用于展示系统的软件三层结构,而不用考虑每个类的具体实现细化阶段:迭代 2
设计模型 用例实现
UP定义:在设计模型中用协作的对象描述如何实现一个特定用例。
一个用例实现就是一个特定场景的对象协作。
在分析的过程中使用用例模型,SSD确定了系统事件;进而系统执行了一个用例场景,这就是一个用例实现。
顺序图中的对象是由领域模型中的概念类启发命名的软件对象,也包括为了设计而引入的纯虚构对象,顺序图描述这些对象协作完成任务的交互过程。
在 POS机案例中发现的系统事件
makeNewSale()
enterItem()
endSale()
makePayment()
细化阶段:迭代 2
对象设计 makeNewSale -1
操作 makeNewSale()
交叉引用 用例,Process Sale
前置条件 无后置条件 1.创建一个 Sale实例 s(实例创建)
2.s和 Register建立关联(关联形成)
3.初始化 s的属性(属性修改)
问题 1:如何为系统操作 makeNewSale选择控制器解决方案,根据“控制器”模式,要么使用“外观控制器”,
要么使用“用例控制器”。在本案例中,由于只存在少量的系统操作,为此选用“外观控制器”,
以 Register作为设计模型中的软件对象。
注意:此时的 Register已经不再是物理终端设备细化阶段:迭代 2
对象设计 makeNewSale -2
设计用例实现过程
Register对象根据系统事件 makeNewSale创建 (使用创建者模式 )
软件对象 Sale
当创建 Sale对象时,还需要为创建一个空集合来记录销售的商品条目 SaleLineItem。
细化阶段:迭代 2
对象设计 enterItem -1
操作 enterItem(itemID:ItemID,quantity:integer)
交叉引用 用例,Process Sale
前置条件 有一个销售正在进行后置条件 1.创建一个 SalesLineItem实例 sli(实例创建)
2.sli和当前的 Sale建立关联(关联形成)
3.sli.quantity变成参数 quantity(属性修改)
4.实例 sli在 itemID匹配的基础上与
ProductSpecification建立关联(关联形成)
问题 1:控制器类的选择问题 2:是否要显示商品的描述和价格?
问题 3:创建新的 SaleLineItem
问题 4:寻找 ProductDescription
细化阶段:迭代 2
对象设计 enterItem -2 顺序图细化阶段:迭代 2
对象设计 endSale -1
操作 endSale()
交叉引用 用例,Process Sale
前置条件 有一个销售正在进行后置条件 1.Sale.isComplete变为真(属性修改)
问题 1:控制器类的选择问题 2:设置 isComplete属性问题 3:计算销售总额主要成功场景:
3、收银员输入商品标识
4、系统逐条记录出售的商品条目 …
收银员重复 3-4,直到结束
5、系统显示销售总额细化阶段:迭代 2
对象设计 endSale -2
1,谁应该负责了解销售总额?
2,销售总额 =所有销售商品条目小计之和
3,小计 =数量 *产品价格 1,ProductDescription.price -> ProductDescription
2,SaleLineItem.quantity -> SaleLineItem
3、所有当前 Sale中的 SaleLineItem -> Sale
问题 1、谁应该负责计算销售总额呢?
解答:根据信息专家模式,应该是 Sale,因为它知道计算总额所必需的所有 SaleLineItem实例。为此 Sale将具有获得总额的职责,并赋予
getTotal方法问题 2、谁应该负责计算销售小计呢?
解答:根据信息专家模式,应该是 SaleLineItem,因为它知道销售数量和与之关联的 ProductDescription。给其赋予 getSubTotal方法。
问题 3、谁负责知道销售商品的价格呢?
解答,ProductDescription,因为它封装了价格的属性,并赋予 getPrice方法细化阶段:迭代 2
对象设计 endSale -3
设计 Sale.getTotal
由 Register发送 getTotal消息给 Sale对象实例
Sale给每个相关的 SaleLineItem对象实例发送 getSubTotal消息
SaleLineItem 又给其相关的 ProductDescription对象实例发送
getPrice消息
<<method>>
Public int getTotal()
{
int tot=0;
for each SaleLineItem,sli;
tot = tot + getSubTotal();
return tot;
}
细化阶段:迭代 2
对象设计 makePayment 1
操作 makePayment(amount:Money)
交叉引用 用例,Process Sale
前置条件 有一个销售正在进行后置条件 1.创建一个 Payment实例 p(实例创建)
2.p.amountTendered等于 amount(属性修改)
3.p和当前的 Sale建立关联(关联形成)
4.当前的 Sale和 Store建立关联(关联形成):目的是向已完成销售的历史日志中添加。
主要成功场景,
……
6.收银员请顾客付款。
7.顾客支付,系统处理支付。
8.系统记录完整的销售信息,并将销售和付款信息发送到外部的记帐系统和库存系统。
9.系统打印收据
……
细化阶段:迭代 2
对象设计 makePayment 2
问题 1:创建 Payment
问题 2:记录 sale日志
问题 3:计算余额处理支付计算余额细化阶段:迭代 2
将 UI层连接到领域层
一种方法:初始化例程(如 Java的 main方法)创建一个 UI和一个领域对象,并将领域对象传递给 UI。
另一种方法:一个 UI对象从一个大家熟知的源(如工厂对象)抽取领域对象。
public class Main {
public static void main( String[] args ) {
Store store = new Store();
Register register = store.getRegister();
ProcessSaleJFrame frame = new ProcessSaleJFrame( register );
...
}
}
细化阶段:迭代 2
初始化和“启动”用例
初始化的领域对象是什么?
应选择位于或接近于领域对象包含或聚合层次中的根类作为初始领域对象。该类可能是外观控制器,比如 Register,
也可能是容纳所有或大部分其他对象的某些对象,比如
Store。
应用如何启动?
建立一个初始领域对象,由它负责后续直接领域对象的创建。
应用发送 create消息以创建初始领域对象
如果初始领域对象控制进程,则应用继续发送 run消息给初始领域对象,移交应用控制权。
细化阶段:迭代 2
POS机案例 设计 Store.create
根据以上原则和分析结果,可有如下初始化内容:
创建 Store,Register,ProductCatalog,ProductDescription
建立 ProductCatalog,ProductDescription关联
建立 Store与 ProductCatalog的关联
建立 Store与 Register的关联
建立 Register 与 ProductCatalog
细化阶段:迭代 2
设计类图
顺序图(用例实现,UCR)将处理系统消息的职责分配给设计类,DCD( Design Class Diagram)则将处理消息的职责转化为对象的方法。
顺序图和 DCD经常并行交叉进行:画一部分顺序图,然后更新 DCD,再进一步扩展顺序图,依此类推。
DCD主要定义类的接口,不定义算法。
基本的属性和关联
类的接口:方法
属性类型、属性可见性、对象导航等。
细化阶段:迭代 2
设计类图步骤 -1
STEP1:通过扫描所有的顺序图以及领域模型中涉及的类,识别参与软件解决方案的类。
Register,ProductCatalog,Store,Payment
Sale,ProductSpecification,SalesLineItem
细化阶段:迭代 2
设计类图步骤 -2
STEP2:根据在领域模型中已经识别出来的部分属性,绘制适当的类图。
注意:角色类未被转换到设计模型中。如,Cashier。
细化阶段:迭代 2
设计类图步骤 -3
STEP3:添加方法,
通过顺序图获得每一个类的方法;
一般,发送给类 X
的所有消息的集合就是类 X必须定义的大多数方法。
细化阶段:迭代 2
设计类图步骤 -3.1
添加方法要注意的几个问题:
创建 create消息一般被忽略,因为在编程语言中,每个类都有相应的构造函数来实现对象的创建。 (但要注意构造函数的参数)
为了实现封装性,每个对象一般都有简单的存取私有成员的 get和 set方法,这些方法是显然的,为了不干扰类图的可读性,不列出 存取 方法。
发送给多对象的消息,处理消息的操作不是多对象中的每一个对象的方法,而是容纳这些对象的容器对象。
细化阶段:迭代 2
设计类图步骤 -4
STEP4:添加更多的类型信息
属性类型
方法参数类型以及返回类型细化阶段:迭代 2
设计类图步骤 -5
STEP5:添加关联和导航
导航( navigability):是关联角色的一个属性,表示从一个源对象沿着关联导航方向可以单向地到达一个目标类。
导航意指可见性--通常是属性可见性。
在 OOPL中,从 A关联导航到 B,转换为类 A中有一个类 B的实例属性。
定义 A到 B带导航修饰关联的常见情况:
A发送一个消息到 B
A创建一个 B的实例
A需要维护到 B的一个连接
往往:一个对象的创建者要求拥有一个与它所创建对象的不间断连接。
细化阶段:迭代 2
POS机案例 较为完善的 DCD
细化阶段:迭代 2
设计类图步骤 -6
STEP6:添加依赖关系
依赖关系:表示一个元素(类、用例等)对另外一个元素有所了解
(用带箭头的虚线表示)。
在类图中常用依赖关系表示类之间的非属性可见性(参数可见性、本地可见性、全局可见性)。属性可见性用关联和导航表达。
细化阶段:迭代 2
设计类图步骤 -7
STEP7:类成员的细节表示(可选)
成员可见性:惯例:属性私有,方法公有。
方法体描述:注释。
细化阶段:迭代 2
设计类图总结
1
2
3
4
细化阶段:迭代 2
设计转换为代码 -1
由 DCD创建类的定义细化阶段:迭代 2
设计转换为代码 -1.1
添加引用( reference)属性
对象的引用属性是指引用另外一个对象作为自己的属性,而不是指简单类型的属性。
一个类的引用属性由类图中的关联和导航来建议
一般来讲:这个引用属性的名称就是导航方向的角色名称细化阶段:迭代 2
设计转换为代码 -2
从交互图创建方法
交互图显示了响应方法调用而发送的消息,这些消息的顺序将转换为方法定义中的一系列语句。
细化阶段:迭代 2
设计转换为代码 -3
多对象的容器类 /集合类在代码中的体现
一个对象维护一组对其他对象的可见性通常是必要的;
在类图中体现在多重性大于 1的一端。
细化阶段:迭代 2
设计转换为代码的次序建议类的实现要按照从低耦合到高耦合度最高的次序来完成