面向对象的分析与设计(收藏)
什么是对象?对象由映象和意向组成。所有的东西——包括物体、事物、活动、关系以及由它们组成的混合体都可以看成是对象吗?对。对象的任何子集都可以看成是对象吗?对,对象具有可分解性,也有可组合性。
什么是类?类是对象的一般性描述。类和对象有什么区别?正如你所见,类描述了对象的特性和行为,也即对象的映象和意向。类是语言描述,由它向我们表达对象信息,或者我们通过定义类来确定对象的特性行为。对象根据类描述而被构造。
什么是面向对象的分析?采用面向对象的思想,对系统进行分析,根据用户需求提取出系统应具有的属性和行为。
什么是面向对象的设计?将分析的结果用某种易于转化为编码或易于理解的形式表达出来。我们常见的有流程图,ER图,数据流图等。分析和设计是两个相互结合、渐进的过程。
[注意] 本文不对“系统”和“对象”作区分,系统被当作对象来处理。所以,文章中出现的“对象”和“系统”可能是指同样的东西。
文章仅代表作者的观点。如果你同意作者的观点,或者觉得文章表达出来的思想不错,欢迎你与作者交流心得体会;如果你觉得文章存在很多错误,或者说其思想本身并无多大可取之处,欢迎提出批评意见。
关于作者对面向对象的理解,请参见《也谈面向对象》和《再谈面向对象》两文。本文是以上两篇文章的延伸。
图书管理系统
这是作者曾经参与的一个项目。该思想在后来的项目中都得到充分的体现。为了便于说明,例子在原来的基础上作了简化。
1.问题描述
图书管理系统由两大部分组成,即数据存储和操作管理两大部分。数据库包括书籍数据库、读者数据库。管理方面包括借书/还书/查询管理、读者/书籍管理。
2.任务与规则
图书管理系统处理读者的注册与注销,新书入库/旧书出库,书籍的借阅与归还,以及读者信息/书籍信息查询等。
规则是系统在运行过程中,管理各种操作应当遵守的约定。如某读者A,读者类型为教师,该类型对应的规则是:最大借书数目为a,最长借书期限为b等。
3.面向对象的系统分析
经过初步分析,系统的E—R图如下。方框图表示实体,直线表示关系。每个实体都可以看作是系统属性。所谓系统属性,是指系统中可以直观表示出来的组成部分,它是系统的一个划分,跟后面介绍的对象划分有关系又有区别。
如你所见,为了降低对象间的耦合度,“借书”和“还书”只与“书籍管理”进行通信。同时对读者信息的更新操作由“书籍管理”通知“读者管理”进行。
如何进行系统分析
怎么样对系统分析,没有一定的标准。很多软件工程著作都给出了步骤,甚至详细规定了你每一步要做的事情。但实际上,你所面对的每一个问题都是不相同的,没有一套万能的方法,能够使你的工作变得既简便,又有最高的效率,还能达到最好的结果。那么,能指导你的,是你的思想,你的经验。同样,我也不能在这里说你应当怎样,不应当怎样。我只希望通过本例来表达系统分析的一些基本思想。
首先,提取系统属性。通过参与某校图书馆的日常工作,及工作人员的介绍,我们知道了图书馆的一般工作流程,了解了图书馆的各个组成部分,及各个部门。根据这些信息,我们提取了该系统的属性:(1)书库及书籍出/入库管理部门;(2)读者信息档案及管理部门;(3)还书柜台;(4)借书柜台;(5)读者及书籍信息查询室。
其次,确定系统行为。图书馆需要做的工作有:读者的注册与注销,新书入库/旧书出库,书籍的借阅与归还,以及读者信息/书籍信息查询等。
还有很多细节问题,可能在开始时无法考虑周全。我们要给这些问题留出解决空间。
不管你采用什么样的方法进行分析,这两个过程一般都会有的。
以看出,这个对象还是个复杂的大对象。我们不能用简单的一个或几个类来描述它。我们也不能一下子设计出成百上千个类来直接实现它——那样,项目会进入到一个无法控制的地步。
4.对象划分:分而治之
既然是一个复杂对象,我们就不可能直接实现它,而是采取分而治之的思想将大规模问题划分成相对独立的小规模问题,逐个击破,来解决大问题。分治思想在很多领域都有广泛的应用,它是实现问题由复杂向简单转化的一个有效方法。在我们的软件设计领域,很多项目都要涉及都众多方面,而且其间关系也异常复杂。建立适当的模型,使我们的问题更易于理解,易于实现,易于维护,是每个分析设计人员的目标。分治思想是很好的思想,特别是在与面向对象思想结合情况下。在本例中,图书管理系统被划分成以下几个子系统:
(1) 借书管理系统;
(2) 还书管理系统;
(3) 查询系统;
(4) 读者管理系统;
(5) 书籍管理系统。
如何进行对象的划分
对对象进行划分,也是没有一定的标准的。从分析人员的角度,它主要取决于三个方面:1、分析人员对面向对象思想(或其他指导思想,采用面向对象方法就是面向对象思想了)的理解;2、分析人员的系统分析经验;3、分析人员对系统构架、用户需求的把握程度。如何将一个大系统划分成相对独立、易于处理的小系统?不同的人可能会得出不同的结果。一般采用E-R转换规则,即将实体看作为一个子系统。但这并不适用于所有的情况。虽然划分没有一定的标准,但评判却是有一定的标准的,即:1、子对象之间独立性要高,即耦合度尽量达到最低,(理想的情况是达到组件化的程度);2、子对象相对其他划分方法,更易于处理。所以对于复杂的大系统,一般都要经过多次的尝试,以尽量能找到较优的划分方案。对于比较简单的系统,E-R转换也能的到较为满意的划分。
其实,我们要划分的子系统,都已经包含在系统分析所得出来的结果——系统属性中。你可以将每个系统属性当作子系统;但这样做可能不是一个好的划分。你可以由这步开始进行对象划分。你可能要合并或分解某些系统属性。
5.制定协议
子对象已经划分,我们如何来实现它们之间的联系?这就要求我们为对象模块间的通信制定一个协议。通信协议是实现模块低耦合的一个有效方法。一般有两种方法可以解决这个问题。
(1) 采用消息机制。即一个对象向其协作对象发送消息,通知该对象进行某种操作。例如,它们的一种使用格式可能是这样的:
PostMessage(FROM SRC, TO TARGET, MSG CODE, PARAM1,PARAM2...)
在这种情况下,就要求我们定义各种消息,以及对应不同的消息时,各个参数的意义。如,一个借书消息格式可能如下:
PostMessage((FROM)借书系统,(TO)书籍管理系统,(MSG CODE)“借书”,(参数1)读者代码(借书证号),(参数2)书籍馆藏号,(参数3)错误信息,RESERVED1,RESERVED2,…)
书籍管理系统在接收到这条消息后,分别向读者管理系统和书籍管理系统发送相应消息。两者都成功,则再向借书系统返回成功信息,否则返回出错信息。
(2) 采用接口。既通过公布对象的接口来向外界提供相应的功能。如,假设书籍管理系统向外界提供了IBookMS接口,那么在借书系统中借书操作的实现看起来应该像这样:
IBookMS bms;
Result rs=bms.Borrow(读者代码,书籍馆藏号);
当然,在进行借书操作前应先注册借书系统实体,如,可能是这样的形式:
RegisterBookMS(IBookMS ibms){ this.bms = ibms; }
接口可有多种类型:单接口,多借口。单接口是指一个对象为向外界发布自己的功能而作的描述;而多接口是指多个协作对象系统同时都需要实现接口,但并不常用。
两种方法各有各的优点。对于前者,源系统可以向目标系统发送任何消息,而不管它能否识别;目标系统只要处理自己感兴趣的消息。这就使得进行增加系统功能等修改时,只需要增加新消息,而不必修改原有消息集。而对于后者,使用简单直观方便,不用去记住不同消息对应的参数含义,不会出现消息丢失使得操作无法完成等的情况。但是在进行系统维护时,可能要修改接口或增加接口,很有可能导致版本控制异常。所以采用哪种方法需要视情况而定,可能选其一,也可能二者兼用。
什么时候使用接口
有些情况下,程序员可能会有两种关于接口的认识,一是很少或从来不用接口,认为接口似乎并没有什么用处,不用接口也能完成任务。二是喜欢滥用接口。确实在很多情况下接口为我们在程序设计时提供了一定的灵活性。但这会造成一些问题,你的代码会因此而变得混乱不堪,难以管理,难以理解,也会使得问题复杂化。
那么究竟什么时候该使用接口?从某个角度说,接口是用来描述对象所具有的特性行为的,是对象代理,可以成为对象间联系的纽带。当两个对象相对独立,但又要进行必要的通信时,就可以使用接口来协助。此外,不要随意使用接口,因为那样可能给你带来问题。下面我说明“必要”的含义。
我们在划分对象之后,就开始对对象的行为特性做进一步的分析处理。如,在上面的例子中,借书系统向书籍管理系统提出借书请求,这是“必要”的通信。但是,在时间处理时我们要考虑读者代码(借书证号)和书籍馆藏号的合法性。那么,这步合法性检验,应该在借书操作之前由借书管理系统进行呢,还是在借书操作时由书籍管理系统进行?似乎两种方案都可以。但是,前种方案会破坏对象划分规则:尽量低耦合。因为,在逻辑上应该由其协作对象处理而不是必要由它处理的事情被它处理,将增加该对象和其协作对象间的耦合度,因为我们希望尽量减少跨对象调用。在上例中,合法性检验放在bms.Borrow (ReaderNumber,BookNumber)方法中,比起在bms.Borrow之前调用如bms.IsIllegalNumber的合法性检验方法更能降低对象耦合度。因此,我们把前一种情况称为“非必要通信”。我们在设计对象的行为特性时,要尽量减少这种非必要通信的发生。
我们可能经常分析名家名作。每当遇到接口时,就要注意它的两端是什么,它所担负的使命是什么。注意到这一点,对我们了解别人的设计思路是很有帮助的。JAVA类库采用了良好的设计模式,它里面就使用了很多接口。不妨去研究研究,也许你会有所启发。
6.逐步求精
你参与了图书管理系统的分析。现在,主管将其中的一个子系统交给你完成,假设就是书籍管理系统。你清楚地知道这个系统应该向其他系统提供什么功能,也知道除此之外其自己还要实现什么功能。那么,从现在开始,你和你的小组成员开始对分配下来的子系统进行分析设计。
BOOK
DB
DBMS
出库
入库
借出
归还
查询
IBookMS : Wrapper包装类
其实对这个系统来说,到这个程度已经非常简单了。我们可以通过几个类实现相应的功能了。如果你的系统还是比较复杂,那么对该系统继续进行3、4、5、6的步骤,直到所有的子系统都比较简单易于实现为止。
7.分布式处理
现在的系统,恐怕很少有针对单机的。在设计计算机通信模块时(假如需要自己实现),要高度封装底层细节。具体我也就不多说什么了。
[总结]
面向对象的系统分析设计,看起来其实也很简单,步骤大概如下:
(1) 从项目开始,进行步骤(2)。
(2) 对系统进行分析,如果它在一定的要求下可解决,则停止分析,进行设计;如果它在一定的要求下不可解决,则对它进行划分。
(3) 步骤(2)如果有分析结果,则对其中每一个子对象,进行步骤(2)。
边界条件(也即上面提到的“一定要求”,对象划分的原则):
子对象之间独立性要高,即耦合度尽量达到最低,(理想的情况是达到组件化的程度);
子对象相对其他划分方法,更易于处理(如实现,维护等)。
说得这么简单,在实际的工作中可就没这么简单了。你可能会碰到方方面面的问题;甚至可能会被好多细节问题淹没。有时候看上去一马平川,出现问题时可能山重水复,不能前行半步。说到底,面向对象思想(或是任何其他思想)只能指导我们工作,让我门不至在繁难复杂的问题中迷失方向;它不是万能的,不会因为采用了面向对象的思想进行分析设计,项目就一定会成功(尽管它确实能在一定程度上使问题简化)。
写到这里,作者还想多说几句。任何思想,都是为了解决某一类问题而出现的,本身并无多大的一般性。所以,面对如潮水般涌来的新思想、新技术,不要被吓着,也不要盲目去追求。选择合适的,与你原有的思想、技术进行比较,扬弃,整合,变成自己的思想。好了,你现在了解面向对象思想了吧?可以去看看其他东西了,CMM,UML算老的了,现在比较热的,AGILE,XP,MDA,AOP,都不知道是些什么。
什么是类?类是对象的一般性描述。类和对象有什么区别?正如你所见,类描述了对象的特性和行为,也即对象的映象和意向。类是语言描述,由它向我们表达对象信息,或者我们通过定义类来确定对象的特性行为。对象根据类描述而被构造。
什么是面向对象的分析?采用面向对象的思想,对系统进行分析,根据用户需求提取出系统应具有的属性和行为。
什么是面向对象的设计?将分析的结果用某种易于转化为编码或易于理解的形式表达出来。我们常见的有流程图,ER图,数据流图等。分析和设计是两个相互结合、渐进的过程。
[注意] 本文不对“系统”和“对象”作区分,系统被当作对象来处理。所以,文章中出现的“对象”和“系统”可能是指同样的东西。
文章仅代表作者的观点。如果你同意作者的观点,或者觉得文章表达出来的思想不错,欢迎你与作者交流心得体会;如果你觉得文章存在很多错误,或者说其思想本身并无多大可取之处,欢迎提出批评意见。
关于作者对面向对象的理解,请参见《也谈面向对象》和《再谈面向对象》两文。本文是以上两篇文章的延伸。
图书管理系统
这是作者曾经参与的一个项目。该思想在后来的项目中都得到充分的体现。为了便于说明,例子在原来的基础上作了简化。
1.问题描述
图书管理系统由两大部分组成,即数据存储和操作管理两大部分。数据库包括书籍数据库、读者数据库。管理方面包括借书/还书/查询管理、读者/书籍管理。
2.任务与规则
图书管理系统处理读者的注册与注销,新书入库/旧书出库,书籍的借阅与归还,以及读者信息/书籍信息查询等。
规则是系统在运行过程中,管理各种操作应当遵守的约定。如某读者A,读者类型为教师,该类型对应的规则是:最大借书数目为a,最长借书期限为b等。
3.面向对象的系统分析
经过初步分析,系统的E—R图如下。方框图表示实体,直线表示关系。每个实体都可以看作是系统属性。所谓系统属性,是指系统中可以直观表示出来的组成部分,它是系统的一个划分,跟后面介绍的对象划分有关系又有区别。
如你所见,为了降低对象间的耦合度,“借书”和“还书”只与“书籍管理”进行通信。同时对读者信息的更新操作由“书籍管理”通知“读者管理”进行。
如何进行系统分析
怎么样对系统分析,没有一定的标准。很多软件工程著作都给出了步骤,甚至详细规定了你每一步要做的事情。但实际上,你所面对的每一个问题都是不相同的,没有一套万能的方法,能够使你的工作变得既简便,又有最高的效率,还能达到最好的结果。那么,能指导你的,是你的思想,你的经验。同样,我也不能在这里说你应当怎样,不应当怎样。我只希望通过本例来表达系统分析的一些基本思想。
首先,提取系统属性。通过参与某校图书馆的日常工作,及工作人员的介绍,我们知道了图书馆的一般工作流程,了解了图书馆的各个组成部分,及各个部门。根据这些信息,我们提取了该系统的属性:(1)书库及书籍出/入库管理部门;(2)读者信息档案及管理部门;(3)还书柜台;(4)借书柜台;(5)读者及书籍信息查询室。
其次,确定系统行为。图书馆需要做的工作有:读者的注册与注销,新书入库/旧书出库,书籍的借阅与归还,以及读者信息/书籍信息查询等。
还有很多细节问题,可能在开始时无法考虑周全。我们要给这些问题留出解决空间。
不管你采用什么样的方法进行分析,这两个过程一般都会有的。
以看出,这个对象还是个复杂的大对象。我们不能用简单的一个或几个类来描述它。我们也不能一下子设计出成百上千个类来直接实现它——那样,项目会进入到一个无法控制的地步。
4.对象划分:分而治之
既然是一个复杂对象,我们就不可能直接实现它,而是采取分而治之的思想将大规模问题划分成相对独立的小规模问题,逐个击破,来解决大问题。分治思想在很多领域都有广泛的应用,它是实现问题由复杂向简单转化的一个有效方法。在我们的软件设计领域,很多项目都要涉及都众多方面,而且其间关系也异常复杂。建立适当的模型,使我们的问题更易于理解,易于实现,易于维护,是每个分析设计人员的目标。分治思想是很好的思想,特别是在与面向对象思想结合情况下。在本例中,图书管理系统被划分成以下几个子系统:
(1) 借书管理系统;
(2) 还书管理系统;
(3) 查询系统;
(4) 读者管理系统;
(5) 书籍管理系统。
如何进行对象的划分
对对象进行划分,也是没有一定的标准的。从分析人员的角度,它主要取决于三个方面:1、分析人员对面向对象思想(或其他指导思想,采用面向对象方法就是面向对象思想了)的理解;2、分析人员的系统分析经验;3、分析人员对系统构架、用户需求的把握程度。如何将一个大系统划分成相对独立、易于处理的小系统?不同的人可能会得出不同的结果。一般采用E-R转换规则,即将实体看作为一个子系统。但这并不适用于所有的情况。虽然划分没有一定的标准,但评判却是有一定的标准的,即:1、子对象之间独立性要高,即耦合度尽量达到最低,(理想的情况是达到组件化的程度);2、子对象相对其他划分方法,更易于处理。所以对于复杂的大系统,一般都要经过多次的尝试,以尽量能找到较优的划分方案。对于比较简单的系统,E-R转换也能的到较为满意的划分。
其实,我们要划分的子系统,都已经包含在系统分析所得出来的结果——系统属性中。你可以将每个系统属性当作子系统;但这样做可能不是一个好的划分。你可以由这步开始进行对象划分。你可能要合并或分解某些系统属性。
5.制定协议
子对象已经划分,我们如何来实现它们之间的联系?这就要求我们为对象模块间的通信制定一个协议。通信协议是实现模块低耦合的一个有效方法。一般有两种方法可以解决这个问题。
(1) 采用消息机制。即一个对象向其协作对象发送消息,通知该对象进行某种操作。例如,它们的一种使用格式可能是这样的:
PostMessage(FROM SRC, TO TARGET, MSG CODE, PARAM1,PARAM2...)
在这种情况下,就要求我们定义各种消息,以及对应不同的消息时,各个参数的意义。如,一个借书消息格式可能如下:
PostMessage((FROM)借书系统,(TO)书籍管理系统,(MSG CODE)“借书”,(参数1)读者代码(借书证号),(参数2)书籍馆藏号,(参数3)错误信息,RESERVED1,RESERVED2,…)
书籍管理系统在接收到这条消息后,分别向读者管理系统和书籍管理系统发送相应消息。两者都成功,则再向借书系统返回成功信息,否则返回出错信息。
(2) 采用接口。既通过公布对象的接口来向外界提供相应的功能。如,假设书籍管理系统向外界提供了IBookMS接口,那么在借书系统中借书操作的实现看起来应该像这样:
IBookMS bms;
Result rs=bms.Borrow(读者代码,书籍馆藏号);
当然,在进行借书操作前应先注册借书系统实体,如,可能是这样的形式:
RegisterBookMS(IBookMS ibms){ this.bms = ibms; }
接口可有多种类型:单接口,多借口。单接口是指一个对象为向外界发布自己的功能而作的描述;而多接口是指多个协作对象系统同时都需要实现接口,但并不常用。
两种方法各有各的优点。对于前者,源系统可以向目标系统发送任何消息,而不管它能否识别;目标系统只要处理自己感兴趣的消息。这就使得进行增加系统功能等修改时,只需要增加新消息,而不必修改原有消息集。而对于后者,使用简单直观方便,不用去记住不同消息对应的参数含义,不会出现消息丢失使得操作无法完成等的情况。但是在进行系统维护时,可能要修改接口或增加接口,很有可能导致版本控制异常。所以采用哪种方法需要视情况而定,可能选其一,也可能二者兼用。
什么时候使用接口
有些情况下,程序员可能会有两种关于接口的认识,一是很少或从来不用接口,认为接口似乎并没有什么用处,不用接口也能完成任务。二是喜欢滥用接口。确实在很多情况下接口为我们在程序设计时提供了一定的灵活性。但这会造成一些问题,你的代码会因此而变得混乱不堪,难以管理,难以理解,也会使得问题复杂化。
那么究竟什么时候该使用接口?从某个角度说,接口是用来描述对象所具有的特性行为的,是对象代理,可以成为对象间联系的纽带。当两个对象相对独立,但又要进行必要的通信时,就可以使用接口来协助。此外,不要随意使用接口,因为那样可能给你带来问题。下面我说明“必要”的含义。
我们在划分对象之后,就开始对对象的行为特性做进一步的分析处理。如,在上面的例子中,借书系统向书籍管理系统提出借书请求,这是“必要”的通信。但是,在时间处理时我们要考虑读者代码(借书证号)和书籍馆藏号的合法性。那么,这步合法性检验,应该在借书操作之前由借书管理系统进行呢,还是在借书操作时由书籍管理系统进行?似乎两种方案都可以。但是,前种方案会破坏对象划分规则:尽量低耦合。因为,在逻辑上应该由其协作对象处理而不是必要由它处理的事情被它处理,将增加该对象和其协作对象间的耦合度,因为我们希望尽量减少跨对象调用。在上例中,合法性检验放在bms.Borrow (ReaderNumber,BookNumber)方法中,比起在bms.Borrow之前调用如bms.IsIllegalNumber的合法性检验方法更能降低对象耦合度。因此,我们把前一种情况称为“非必要通信”。我们在设计对象的行为特性时,要尽量减少这种非必要通信的发生。
我们可能经常分析名家名作。每当遇到接口时,就要注意它的两端是什么,它所担负的使命是什么。注意到这一点,对我们了解别人的设计思路是很有帮助的。JAVA类库采用了良好的设计模式,它里面就使用了很多接口。不妨去研究研究,也许你会有所启发。
6.逐步求精
你参与了图书管理系统的分析。现在,主管将其中的一个子系统交给你完成,假设就是书籍管理系统。你清楚地知道这个系统应该向其他系统提供什么功能,也知道除此之外其自己还要实现什么功能。那么,从现在开始,你和你的小组成员开始对分配下来的子系统进行分析设计。
BOOK
DB
DBMS
出库
入库
借出
归还
查询
IBookMS : Wrapper包装类
其实对这个系统来说,到这个程度已经非常简单了。我们可以通过几个类实现相应的功能了。如果你的系统还是比较复杂,那么对该系统继续进行3、4、5、6的步骤,直到所有的子系统都比较简单易于实现为止。
7.分布式处理
现在的系统,恐怕很少有针对单机的。在设计计算机通信模块时(假如需要自己实现),要高度封装底层细节。具体我也就不多说什么了。
[总结]
面向对象的系统分析设计,看起来其实也很简单,步骤大概如下:
(1) 从项目开始,进行步骤(2)。
(2) 对系统进行分析,如果它在一定的要求下可解决,则停止分析,进行设计;如果它在一定的要求下不可解决,则对它进行划分。
(3) 步骤(2)如果有分析结果,则对其中每一个子对象,进行步骤(2)。
边界条件(也即上面提到的“一定要求”,对象划分的原则):
子对象之间独立性要高,即耦合度尽量达到最低,(理想的情况是达到组件化的程度);
子对象相对其他划分方法,更易于处理(如实现,维护等)。
说得这么简单,在实际的工作中可就没这么简单了。你可能会碰到方方面面的问题;甚至可能会被好多细节问题淹没。有时候看上去一马平川,出现问题时可能山重水复,不能前行半步。说到底,面向对象思想(或是任何其他思想)只能指导我们工作,让我门不至在繁难复杂的问题中迷失方向;它不是万能的,不会因为采用了面向对象的思想进行分析设计,项目就一定会成功(尽管它确实能在一定程度上使问题简化)。
写到这里,作者还想多说几句。任何思想,都是为了解决某一类问题而出现的,本身并无多大的一般性。所以,面对如潮水般涌来的新思想、新技术,不要被吓着,也不要盲目去追求。选择合适的,与你原有的思想、技术进行比较,扬弃,整合,变成自己的思想。好了,你现在了解面向对象思想了吧?可以去看看其他东西了,CMM,UML算老的了,现在比较热的,AGILE,XP,MDA,AOP,都不知道是些什么。