首页 > 代码库 > 面向对象分析与设计的一些心得

面向对象分析与设计的一些心得

面向对象分析与设计的一些心得

分析、设计与实现

 

我所理解的真正的对象就是现实生活中客观存在或不存在的真正的对象。这个对象有一个明显的特征就是它具有非常多的状态特征和行为特 征。比如一个人是一个对象,他在一生中会经历无数个交互场景,在这个过程中,每个人的行为特征会不断增多,大部分行为是通过后天学习得到的,只有少数行为 是先天就具有的;另一方面,对于状态特征也是在时不时的变化,比如你的身高、体重,等等。最后,人因为会参与到不同的交互场景,会导致和他关联的各种关联 信息也会不断增多,比如你去上大学,老师给你一张借书卡,此时你就拥有了一张借书卡,可以理解为你多了一个关联信息;哪一天你去参加英语四级考试,考了 70分,然后你拥有了一本四级考试证书,上面写这成绩为70分,此时你也同样多了一个关联信息,就是一本英语四级考试证书。

现实生活中的对象:

  1. 兼具各种场景下的所有状态和行为特征;
  2. 固有状态会时不时的变化,通过参与交互场景还会增加一些关联信息;
  3. 行为会不断增多,一般是通过学习得到;因此,我们从中可以知道,现实生活中的对象肯定不是我们设计软件时候的对象,因为它是如此的复杂,包含了或关联了非常多的状态特征和行为特征;

面向对象分析阶段时的对象

既然是分析阶段,那我们就不要过多的考虑任何设计阶段的思想。我觉得在分析阶段,我们在分析对象时主要考虑两个方面:

  1. 对象的状态特征变化规律;
  2. 对象的行为特征变化规律;

分析阶段,我们往往从某个场景出发,分析该场景中有哪些“对象”,此时的“对象”之所以加双引号是因为它不是真正的对象,而是真正的对象的某个方 面,我们在某个场景下只关心对象的某个方面;我觉得分析阶段的对象和现实生活中的对象应该是一致的,或者至少是逻辑上是一致的。也就是说,在面向对象的分 析阶段,我们应该将现实生活中我们所理解的对象的一切特征在脑子里描述清楚。比如同一个人,它在不同的场景下(一个场景代表了一个考虑问题的边界)会参与 不同的交互活动。 这句话体现的含义是:

  1. 同一个对象会参与不同的场景,行驶各种交互行为;
  2. 同一个对象我们会根据我们不同的认识角度,对同一个对象的关注角度的不同,将其理解为不同的类型或角色;

比如一个人,在家里可能是父亲,在公司可能是职员,在比赛场上可能是运动员。但无论我们给这个人授予什么样的称谓,这个人始终是同一个对象。所 以,在面向对象的分析阶段,对象给我们的感觉是它在不停的变换其类型或角色;上面在谈到什么是真正的现实生活中的对象时提到,对象在参与到交互活动后会多 出一些关联信息,这些信息是属于谁的呢?答案是:这些信息属于扮演了某个角色的对象的;之所以强调扮演了某个角色,是因为想让大家明确对象一定是在扮演某 个角色参与到某个场景交互活动后才具有那些关联信息的。总结:我觉得在面向对象的分析阶段,我们分析的要点是:

  1. 站在现实生活中真正的对象的角度去理解对象的状态特征和行为特征的变化规律;
  2. 理解真正的对象和对象的某一个方面(即我们所关心的“对象”);
  3. 理解同一个对象会扮演不同角色参与到不同交互场景;
  4. 理解对象的关联信息如何产生,关联信息是属于谁的;

面向对象设计阶段时的对象

首先说一下,目前的编程语言实现对象时,是以哪些方式让创建对象的。

C#等基于类型的静态语言,类型规定了对象可以具有的状态特征和行为特征,对象的一切状态和行为都是由其所属的类型确定的;这又一个很明显的好处 时,我们在任何时候都知道对象的类型或接口,从而就能明确知道其数据结构,也就知道对象的状态,从而可以方便的持久化对象的状态或者重建对象;但这种为对 象带来状态和行为特征的设计思路同时也有一个缺点就是对象的类型或其表现出来的接口无法更改。这点是违背“真正的对象”或者“分析阶段的对象”的特征的。

JavaScript等弱类型的动态语言,这种语言认为对象无类型,对象的状态和行为不需要从类型为模板获取,状态和行为可以随时附加到某个对象上。这种思路其实很好,因为很符合上面提到的真正的对象的状态特征与行为特征的变化规律。但是这种语言也有一些致命的缺点:

  1. 由于没有类型,导致无法在使用时明确知道其具有哪些状态和行为,这会增加编程出错的可能性,只有在运行阶段在会检测到访问了不存在的状态或行为;
  2. 同样是弱类型的原因,对象无法被持久化,因为不知道要持久化哪些状态,同样,更不用说重建对象了;所以,基于这两个缺点,我觉得动态语言不适 合在服务器端大量使用去做工程实现业务逻辑,而在一些不需要持久化对象状态的客户端环境,只在内存中处理逻辑的情况下使用这种语言比较适合;

当我们在设计软件时,如果是用C#等静态语言、基于类和接口的语言去设计对象时,该如何设计呢?在设计阶段,我觉得目标就是把分析阶段得到的对象 用尽量平滑的方式转换为设计;需要把握的要点是:1)从一个基本的类创建出对象;2)用尽量平滑的设计思路去支持一个对象表现出不同类型或角色的特点;举 个例子吧:

1var gonn = new Person();
2gonn.Eat(); //吃饭
3var teacher = gonn.ActAs<ITeacher>();//扮演教师角色
4teacher.Teach(); //教书

gonn这个对象首先是一个人,所以从Person这个基本类型中获取基本的状态特征和行为特征(如吃饭);然后当gonn去教书时,他会扮演教 师的角色,扮演之后他就是一个教师了,然后他就具有了教师这个角色所赋予的行为(教书)了。上面的代码看上去和真正的对象在现实生活中的变化规律类似,非 常平滑;这样做有几个好处:

  1. 强类型;
  2. 对象交互模型与现实生活中的交互模型完全一致,所以代码非常容易懂,可读性强;
  3. 对象不会随着参与交互场景的增多而变得臃肿和复杂,因为由于引入了角色的概念,我们将交互模型实现为对象扮演某个角色参与交互活动的方式来设计,做到了对象动态被赋予身份,从而具有与该身份相关的状态特征和行为特征;
  4. 对象参与交互场景后所关联的一些关联信息不会直接存放在对象上,而是放在了“扮演了某个角色的对象”上,在上面的例子中就是teacher对象;

面向对象实现阶段时的对象

那么如何实现这样的设计呢? var teacher = gonn.ActAs<ITeacher>();

其实很简单,可以类用装饰模式来实现,我们都知道,设计模式中的装饰模式可以动态给一个对象增加状态或行为。所以在实现阶段,我们可以设计一个Teacher类,大概设计如下:

01public class Teacher : ITeacher
02{
03    private Person actor;
04 
05    public Teacher(Person actor)
06    {
07        this.actor = actor;
08    }
09 
10    public void Teach()
11    {
12        //do the teach operation.
13    }
14}

teacher就是实现阶段的对象,而Teacher类则是实现阶段的对象的类型;可以看到Teacher类关联了一个Person对象,同时实 现了ITeacher角色接口。所以ActAs,var teacher = xuehua.ActAs<ITeacher>();

这个函数做的事情就是在内部创建一个Teacher类的实例,该实例对当前的Person对象有一个引用,然后返回。也许你会说,返回回来的 teacher对象已经不是原来的xuehua对象了,而是一个新的对象,并且封装了xuehua这个对象;没错,所以说这是实现上的问题。我们在关注业 务时,关心的不是当前对象的真正类型,而是关心对象的状态特征、行为特征,或者技术化一点来讲就是关心对象的交互模型,关心的是对象扮演什么角色在进行交 互。

总结

  1. 找出最关键的一些业务场景。一般通过动词来寻找,比如招聘系统中,一个应聘人投递一个职位就是一次应聘,应聘就是一个业务场景;一个学生参加某门课的考试,那么考试就是一个业务场景;一个学生去图书馆借书,那么借书就是一个业务场景;
  2. 针对每个业务场景分析出有哪些场景参与者,哪些参与者以对象的形式参与,哪些参与者以服务的形式参与。为什么要区分对象还是服务是因为有时候我们不关心参与者是哪个,而只关心参与者是什么。一般服务在系统中我们只关心它是什么服务,并且在系统中服务一般也只有一个实例;而对象则不同,我们会关心对象是谁,即哪一个;
  3. 分析每个场景参与者对象的基本状态特征。所谓的基本状态特征是指对象与生俱来的,对象从一开始被创建出来 之后就具有的状态特征;最形象的例子就是人的身高体重,当人一出生便具有了身高和体重这两个状态特征;再比如一篇博文,它从被写好之时起就具有了内容这个 状态特征,但是我们可以随便修改博文的内容;但是有些状态特征是不能修改的,比如博文的创建时间一旦博文被创建之后便不能再被修改;需要注意的是我们不要 把和对象关联的一些关联信息也认为是对象的基本状态特征。比如某人有一本英语六级考试证书,那么人和证书之间的关系是拥有的关系,证书不是人固有的与生俱 来的基本状态特征,而是人参与了某次考试这个场景后得来的;后面我会提到这种关联关系该如何去思考和理解!
  4. 分析每个场景参与者对象分别扮演什么角色参与场景,整个场景的完整交互过程是怎样的,对象在参与场景的过程中执行了哪些交互行为。 相信大家都明白这点非常重要,因为它涉及到对象之间如何交互,涉及到该如何分析哪个对象该具有哪个交互职责;从而最终决定了在代码级别哪个类该具有哪些方 法的问题。关于这方面思考的例子我会在下面进行介绍,这里先说一下理论吧。我觉得要点是将整个业务场景中的每个交互行为通过四色原型的分析方法来理解。用 一句话来概括四色原型就是:一个什么什么样的人或组织或物品以某种角色在某个时刻或某段时间内参与某个活动。另外一个技巧就是,我们经常可以问自己,这个 交互行为是“谁通知谁做什么事情?“,行为的驱动者是谁?行为的执行者是谁?一般行为的驱动者就是通知方,行为的执行者就是被通知方,被通知方拥有”通知 方要求做的事情“执行行为;另外,我觉得还需要说明的是,现实生活中的对象并不是说其扮演了某个角色后才具有角色所定义的行为的,而是本来就有的,只不过 是在扮演角色后表现出了该行为;所以对于现实生活中的对象,执行角色所定义的行为和扮演角色是同时发生的,没有谁先谁后的说法;但是软件中的对象则不同, 因为软件中的对象只是现实生活中的对象的某一个我们所关心的方面,所以它的能力也有限。另外从设计实现的角度职责单一的角度来说,我们也不会将软件中的对 象设计的很复杂,包含很多职责,因为这样会导致对象难以维护,这样做虽不违背分析原则,但违背设计原则。软件中的对象我们往往是设计成当它扮演某个角色 时,动态给对象注入角色所定义的交互行为,从而给对象赋予了参与场景交互的能力。因此简单的说,软件中的对象,平时只具有基本的状态特征和基本的非交互行 为,而当它扮演某个角色时,则动态具有交互行为;
  5. 分析交互过程结束后分别会对每个场景参与者对象产生哪些基本状态特征的改变。这个很好理解,当一个对象参与了某个交互活动后,一些基本状态特征会发生改变,比如一个人参加一次100米跑步比赛后,心跳速度会加快;心跳速度就是人的基本体征;这个应该很好理解吧,不多举例了。
  6. 分析如何记录和跟踪这一次交互行为,分析这次交互行为会产生哪些额外的信息。这点估计大家平时很少思考, 而这点我想也正是我的面向对象分析思路最具特色的地方吧!大家一定知道,一次对象的交互活动会产生一些与该交互活动相关的交互信息。比如一次应聘活动会产 生一些与该活动相关的信息,如是否录取,笔试成绩,面试成绩等;比如一次考试会产生考试成绩这个信息;一次借书会产生一个借阅信息(包含:借书人、被借的 书、借书时间,我们可能还会设计一个还书时间);并且,在很多情况下,这些交互信息会在后续的其他交互场景中再被更新。比如,一次应聘一开始状态可能是” 新投递“,表示应聘人刚刚投了简历并选了某个职位,后来她去参加笔试或面试了,那么这次应聘的状态就变为了”已笔试“或”已面试“;在比如一个学生参加一 次考试,刚开始还没有成绩,但后来老师批卷子后便有了考试成绩。再比如一次借书后,如果这本书还没被还,则还书时间为空,而一旦还书了之后,便有了还书时 间;因此,我们从这些规律中可以发现,交互其实是一个过程,并且该过程一旦开始后就会产生一些相关的信息,如应聘的状态,考试的成绩,借阅信息的归还时 间,等等。通常我们会把交互过程本身所涉及的一切信息以及交互过程所产生的所有附加信息作为一个整体来进行考虑。所以,我觉得我们有必要设计一个对象,用 来表示某一次交互的结果,这个结果包含交互过程本身所涉及的一切信息以及交互过程所产生的所有附加信息;大家想想,看到”应聘“这个单词,你有时候会认为 它是一个动词,优势后会认为它是一个名词。在认为是动词时,我们关注的是交互本身,活动本身,强调行为;而在认为是名词时,我们关注的是应聘行为所产生的 一切信息;所以,看到这里,我想大家应该心里有个数了,就是在交互行为结束后,我们往往需要设计一个对象用来表示一次交互活动的相关信息;这些信息一方面 体现了交互活动的参与者,(交互时间,交互地点,如果我们关心这些的话),另一方面体现了交互活动所产生的附加的信息,如成绩,应聘状态,借书还书时间, 等等。最后,需要强调的是这些信息之所以设计为对象是因为这些信息不是历史,即不是不可改变的,相反,这些信息会在后续的其他交互活动中被更新;所以,看 到这里,我想我们就不用在纠结对象在参加交互活动后所产生的一些与它关联的信息该如何存放这个问题了。

面向对象分析与设计的一些心得