课件
关于CRC卡片分拣法
CRC是类(Class)、责任(Responsibility) 和协作(Collaboration) 的简称,CRC分析法根据类所要扮演的职责来确定类。
CRC卡片分拣法是从文档中提取类定义的最常用方法之一,通过CRC卡片分拣的过程我们可以了解类的识别和类定义的内容最主要的方面。
CRC卡片分拣法中间包括三个要素,第一个C代表的是类Class,第二个R代表的是Responsibilities,表示类的职责的定义,第三个字母C代表的是Collaborations,表示类与类之间的交互关系。
CRC卡片就是如图中这样的一个表格卡片,它把三个部分分别列出来:
首先我们要提取的是类的定义,然后再给当前定义的类寻找它应该完成的职责,以及和它有交互关系的其它的类。CRC卡片相当于是把我们在问题描述中得到的相关的类都能够找出来,并且定义它的职责,和类与类之间的交互关系。
识别类的方法
识别类的方法主要有:
- CRC卡片分拣法
- 根据用例描述中相应的名词找出候选类
- 根据边界类、控制类、实体类的划分来发现系统中的类
- 根据设计和分析模式也可以帮我们去补全所遗漏掉的类
有许许多多的方法,但是最主要的是我们要对待解决的问题有一个充分的分析之后,把其中关键的信息实体寻找出来。
有哪些东西可以定义为对象呢?
对象几乎是无处不在,它可以是:
- 外部实体:和建模中的系统存在交互,无论他是人、设备还是其它的系统,都可以定义为对象;
- 事物:也可以是建模的应用领域中存在的某种实体事物,比如报表、信号、文字输出等等;
- 事件:还可以是一个动态的发生的动作、事件,系统上下文中发生的资源传递、控制命令发出等等;只要是我们关注的那些活动也可以作为对象来定义;
- 角色、组织单元:在应用领域的组织上下文中出现的由系统交互的人扮演的那些角色,以及系统所管理的相关组织单元的分支机构、群组、团队;
- 位置、地点:我们所关注的要记录属性的相关位置、地点等信息;
- 结构体:系统中我们关注的组合体,比如传感器、车辆、计算机等等;
以上这些都可以用对象来封装,关注它的属性和行为,因此把它定义为应用领域中的对象。
不能定义为对象的事物有2个方面(原因是因为它是对象的组成部分):
- 过程:归结为某个对象的操作过程,比如打印、转换等等
- 属性:在对象内部封装的单一的属性,比如兰颜色,50Mb等
21点游戏 Blackjack
假定我们要编写一个21点游戏的程序,这个时候客户来到你面前交给你一份详细的问题描述,也就是一份21点游戏的规则说明,在我们开始设计这个系统之前,我们先要看看在这个游戏的过程中有哪些关键的类、这些类的职责是什么、类与类之间的交互行为是什么。
游戏规则是:
- 21点游戏的目标是玩家要获得点数最接近或者等于21的手牌
- 他要赢过庄家手牌的点数并且不能超过21点
- 无论桌上有几名玩家,玩家都只是和庄家之间论输赢
- 在21点游戏中,2到10的数字牌,其点数就等于牌面数值
- A的点数根据玩家的需要可以选取1或者11
- J、Q、K的点数都是10
游戏的开始玩家首先下注,通常下注的额度是有上限的。如果你是桌上唯一的玩家,在下注之后庄家就会给你发两张牌,牌面向上,双方均能看到。庄家给自己发两张牌,一张牌面向上(明牌),一张牌面向下(底牌)。当有多个玩家的时候,庄家给每位玩家都是先发一张明牌,庄家给自己发一张底牌,再给每个玩家和庄家各发一张明牌。
发完前两张牌之后,每位玩家自己决定是否还需要更多的牌,当对手上的牌数满意之后就停手等待。停手之后庄家可以继续加牌,但是不能超过21点。当你的手牌点数超过庄家时,你赢,当你的手牌点数不及庄家时,庄家赢。
玩家赢的情况:1、你的手牌点数超过庄家且不超过21点;2、庄家冒过21点;
庄家和玩家点数持平的时候,不输不赢,庄家退还玩家的赌注;
玩家输的情况就是:玩家抓冒了21点;
1、识别对象类
由于类对象通常定义的是一个命名的实体,用名词表达,因此我们在分析文档的时候就先从名词开始,在这个过程中要注意的是不要想一下子把所有的类都找对,而是把所有可能的候选的类都先提取出来,再逐一筛选。
从21点游戏描述中过滤出我们认为关键的、主要的名词或者名词短语:
比如对图1描述的我们过滤出的名词包括:手牌、庄家、玩家、数字牌、点数、A、J、Q、K等。
从图2描述中过滤出的名称有:赌注、上限、底牌、明牌。
因此通过扫描名词的方法我们得到了这些对象类:
2、类筛选
下面要来用一定的方法从这些候选位中找出我们真正想要定义的实体类。那么类的筛选原则是什么呢?
首先在候选类中排除以下类:
- 超出问题关注范围的类
- 指代整个系统的类
- 功能上有所重复的类
- 过于含糊或过于具体的类
可观察到的现象是,实例对象过多过少
在候选的列表中很明显“21点”和“游戏”都是指代整个系统的类,过于庞大,因此可以把它筛掉。A、J、Q、K这些都是非常具体的类,因此也可以筛掉。
初步筛掉一些无关类后,可以进一步问以下问题:
- 是否需要保存对象信息:系统需要保存对象信息吗?
- 是否对外提供所需服务:类对象是否对外提供修改属性值的操作?
- 是否具有多个属性:只有一个属性的类,应该建模为属性
- 是否具有公共属性:类属性是否为所有实例对象共享?
- 是否具有公共操作:类操作是否为所有实例对象共享?
- 是否为外部实体:如果生产或使用对象的信息,也应考虑建模为系统类
经过刚刚的一个筛选过程,我们得到了最后想要的这些类:
筛选掉的有:
- 代表整个系统的类:21点、游戏
- 过于具体的类:A、J、Q、K、数字牌
- 只有单个属性的类:点数、花色、赢家。其中花色因为在21点游戏中我们并不十分关注,因此既可以把它建模为一个属性也可以忽略掉。
由此我们得到了剩下的六个真正想要的候选实体类:庄家、玩家、牌、牌堆、手牌、赌注
3、类识别
从刚刚的过程我们可以看到,在CRC卡片分拣法里类识别的方式是:从原始资料中识别,找出干系人提交给我们的问题描述中的名词和名词短语,在名词和名词短语中找出它描述应用领域中信息的结构或者本质,把它加入到我们考虑的范围中来。
除了用这种方式以外,还可以通过其他来源来识别类的定义,比如相关的背景信息、直接由用户和干系人提供、适用于当前场景下的分析模式中已有的类。
在类识别过程中我们要把握的一个原则是:要尽可能识别出多的侯选类。之后我们再按照一定的标准对它的类的价值和用处进行分析,再筛选这些侯选的类。明确的作出了理性判断以后,排除一个类要比在第一版本中根本不考虑来得更合理。
4、识别类的功能职责
识别出类以后,为每个类分别分配一张卡片,下一步就是针对每个类来识别它的功能职责。由于功能职责总是关乎这个类对象相关的行为和动作,下一步我们就是要问题描述中扫描相关的动词短语。
需要注意的是:
- 并非所有动词均将成为类职责。需要找出那些对我们关注的、刚刚找出的、与侯选类相关的那些动词
- 有时候多个动作可合并为一个职责
- 随着分析过程的深入,会发现新的职责
- 无论是类的定义还是职责的定义都是一个不断修正和提升的过程
- 两个类是在合作完成同一个职责的时候,为二者同时添加该职责
从图3中过滤出的是:赌注、上限、底牌、名牌
图4中没有发现新的名称。
对问题描述进行扫描能获得比较精确的侯选类的识别,但在识别类的功能的时候,问题描述本身只能给我们一些重要的启示,很多过程也是需要我们透过字面去发现、抽取:
比如,对于牌,我们需要知道牌面的数字是什么、花色是什么、对应花色的点数是什么,而要知道点数首先要知道它是数字牌、还是AJQK,因此这里头蕴含的不仅仅是字面能够识别出来的动词,还有我们对整个问题深入的理解。
5、识别类交互协作关系
在识别出侯选类对应的功能职责之后,将进入下一步,即在这些对应的功能职责中找出哪些侯选类之间是要存在交互协作关系、共同完成某些过程和功能。这里使用UML用例图的方法,将交互着的类找出来,但要知道,目的并不是为了写出所有的应用场景,而是为了进一步对类和刚刚的职责定义进行精化。
↑如图举例,通过对这个例子的分析可以进一步完善庄家、玩家和手牌的职责定义。比如庄家首先洗牌玩家下注,庄家发第一张牌玩家拿牌,庄家给自己发牌而手牌则返回庄家手牌的点数,庄家发第二张牌然后询问玩家是否需要继续要牌,玩家根据自己手中的点数觉得是否要牌并根据点数确定何时停止,玩家根据需要决定是否要增加还是减少赌注,最终玩家庄家都亮牌并根据输赢来分配最终的赌金。
通过情景分析,识别出以下交互关系:
进而得到最终的CRC卡片:
1、纸牌类:
功能职责是既要知道它的牌是什么又要知道它的点数是什么。与纸牌交互的类包括牌叠。
2、牌叠类:
功能是重新洗牌、牌叠的牌的张数、拿到牌叠的下十张牌、显示当前的牌叠在屏幕上。跟牌叠交互的类包括庄家和手牌。
3、庄家:
功能职责是决定何时开启下一轮、在牌叠中取一张卡。和庄家交互的类包括手牌、玩家、牌叠。
4、玩家:
功能职责是要牌、拿到下一张牌、亮牌、计算手牌的点数。与之交互的类包括庄家、手牌。
5、手牌
功能职责是返回当前牌的点数、在手牌中增加一张底牌、亮牌。与之交互的类包括庄家、玩家。
6、初步得到UML类图
通过这样的初步分析导到了第一张UML类图,既得到当前类的定义,又得到了类的一部分属性、相关的活动定义,其中的相关操作源自于刚刚的交互过程(即功能职责),属性是指在功能职责过程中用到的那些属性。