面向对象分析与设计
上QQ阅读APP看书,第一时间看更新

1.1.1 对象的基本概念

1.对象和类的定义

在面向对象技术和方法的发展过程中,人们曾经给出了多个不同的对象定义。这些定义不仅描述方式有所不同,其含义也稍有区别,但这些定义的共同点都是把对象看作一个封装了数据和操作的实体。本书采用Grady Booch等人给出的对象的定义。

所谓对象(Object),就是将一组数据和与这组数据有关的操作组装在一起所形成的一个完整的实体。对象中的数据被称为对象的属性,一个对象的所有属性的值被称为这个对象的状态。对象的操作则被称为对象的行为,也称为对象的外部接口。

在特定的软件系统中,对象表示的是软件系统中存在的某个特定实体,它们可以有不同的类型,大多数对象通常是一种对现实世界客观事物的计算机描述。

例如,图书管理系统中的一本图书、一个读者,或一次借阅记录都可以被视为是一个对象。它们分别表示了现实世界中一个图书馆的一本书、一个读者和一个借阅事件。但这些对象并不等同于现实世界中的实际事物,图书管理系统中的这些对象只描述它们在计算机系统中的状态和行为,因此它们不过是对现实世界中对应的客观实体的一种计算机描述而已。

一个现实的系统中,通常会有很多的对象,为了有效地管理这些对象并使之发挥作用,人们又从分类的角度出发,给出了类的定义。

类(Class)可以被定义成具有某些相同的属性和方法的全体对象构成的集合。

这个定义将类看成是对一组具有相同属性和方法的对象的一种抽象描述,其本质在于这些对象所具有的相同属性和方法。因此,类也可以被定义成是一组属性和方法构成的一个整体。

目前的各种主流程序设计语言都采用了这样的方式来定义类。

例如,图1-1就给出读者、图书和借阅记录这三个对象的一个抽象描述。它们分别表示了三个类,当然也表示了这三个类之间的关系。

图1-1 图书管理系统中的图书和读者类图

另外,从类与对象之间的关系的角度来看,类和对象显然分别属于两个不同的抽象层次。与对象相比较,类处于较高的抽象层次,对象则处于较低的抽象层次。

为了描述二者之间的层次关系,面向对象方法使用实例的概念来描述这两个不同的抽象之间的关系。将类看成一个对象集合时,这个集合中的任何一个对象又可以被称为这个类的一个实例(Instance)。一个类通常会有多个实例。在特殊情况下,只有一个对象的类被称为单件类,没有实例的类则被称为抽象类

从这个概念出发,还可以进一步引申出实例化的概念,可以将创建类实例的过程称为实例化。显然,任何对象均需要一个实例化过程,这个过程可能很简单,也可能很复杂。实例化过程的复杂度与类结构的复杂度有关。

从上述定义出发,一个对象就是一个具有某种特定状态和行为的实体。同时,结构和行为类似的对象又被定义在它们共同所属的类中。实例和对象这两个术语可以互换使用。

对象可以表示现实世界中的各种事物,从简单的一个数字到一个复杂的系统等均可看作是一个对象。它既可以表示具体的事物,也可以表示抽象的逻辑事物,如一本图书、一个业务规则、一份计划或一个事件等。

在对象模型中,如果一个对象使用了另一个对象,则将使用者称为客户(Client)对象,称被使用者为服务器(Server)对象。对于任何一个对象来说,这个对象中可以被调用的操作集合以及这些操作的合法调用顺序被称为对象的接口(Interface)或协议(Agreement)。

这个协议描述了对象的动作和反应的方式,构成了对象抽象的和完整的外部视图。

2.对象的状态

对象的状态(Status)是指一个对象的所有属性在某一时刻的取值或一个对象的某些属性值所满足的条件。任何对象的状态都是由这个对象属性值决定的。

一般情况下,一个对象可能会有多个不同的状态。例如,图书管理系统中的图书对象,可能具有借出(Lend)、被预定(Reserve)和就绪(Ready)等几种不同的状态。

对于任何两个对象,只有当它们所拥有的属性及属性值均完全相同时,才可以说这两个对象的状态完全相同,但值得注意的是,两个状态相同的对象不等同于它们是同一个对象。判断两个对象是否为相同的对象,不仅仅要看它们的属性值,还要看它们不同的存在时间和存在空间。

当多个对象拥有相同的属性定义,但属性值却不完全相同时,只能说它们具有相同的表现形式,但不能说它们具有相同的状态。

当多个对象不仅属性值不同,而且拥有属性定义也不尽相同时,则说它们不仅状态不同,并且它们的表现形式(或所属的类)也不相同。

3.对象的行为

软件系统中,每一个对象通常都不是孤立存在的,对象之间还会存在各种各样的联系和影响。

我们将一个对象在其状态发生某种改变或接收到某个外部消息时所进行的动作和做出反应的方式称为对象的行为(Behaviour)。换句话来说,对象的行为代表了它们的外部可见的活动。

在面向对象方法中,通常使用操作来描述对象的行为。一个操作(Operation)可以定义成一个对象向外部提供的某种服务,如果外部对象调用对象的某个操作,这就意味着被调用的对象将执行这个操作中包含的动作,并相应地调整其自身的状态。

例如,调用一个栈对象的“进栈”或“出栈”操作,将改变这个栈的状态(内容和长度),进而影响这个对象下一步可以进行的操作。而调用栈的“长度”操作,则可能仅仅返回一个表示该对象长度的值,但不改变本身的状态。

在面向对象方法中,使用消息(Message)的概念来描述对象之间的交互,并将消息传递作为对象之间唯一的交互方式。在面向对象语言中,通常将调用一个对象的操作称为向这个对象发送一个消息

消息和操作是密切相关的两个不同概念。操作描述的是对象提供的外部服务,消息描述的是两个对象之间的一次交互。而二者之间又是密切相关的,即向一个对象发送一条消息就是调用这个对象的某个操作。因此,在一般情况下,可以不加区别地使用操作和消息这两个术语。

值得注意的是,消息传递只定义了对象行为的一个方面。另一方面,对象的行为还要受到对象自身状态的影响。因此,一个对象所表现出来的行为不仅取决于施加在该对象上面的操作,而且还取决于它的当前状态。换句话说,一个对象的状态不仅包括它的属性和属性值,还包含它的先前行为的累积效果。

例如:自动饮料售货机的使用方法:首先,顾客投入货币,然后选择饮料,售货机为顾客提供需要的饮料,顾客按找零按钮,售货机为顾客计算并返回余额。如果顾客先选择饮料后投币会发生什么?售货机很可能会什么也不做,因为顾客违反了它的基本操作流程。顾客选择饮料时售货机只是处于等待投币的状态,此时售货机很有可能会忽略或屏蔽了顾客选择饮料的操作。

这个例子说明,对象的行为往往要受到它当前状态的影响,与一个对象进行交互时,必须了解该对象所处的当前状态,同时还要了解操作的后置状态及进一步可以进行的操作。

操作的这种状态相关特性引出了操作的前置条件和后置条件这两个重要的概念。操作的前置条件(Pre-Condition)是指对象执行这个操作时所必须具有的状态或必须满足的条件,违反了前置条件将导致该操作不能正常执行。

同样,操作的后置条件(Post-Condition)是指这个操作执行完以后对象所应处的状态或满足的条件,违反了后置条件意味着这个人操作没有完成应尽的责任。因此,为对象的操作建模时,清楚地描述每个操作的前置条件和后置条件无疑是十分重要的。

下面再来介绍操作的分类问题。

一个对象通常会有多个操作,每个操作都代表了这个对象所属的类提供给这个对象本身的一种服务。按照操作的作用,可以将操作划分成构造析构修改选择遍历五种不同的类型。其中构造和析构是默认操作,分别用于对象的创建和销毁,后三种类型则表示三种不同类型的常见操作。

修改(Modify)操作是一种能够更改一个对象本身状态的操作。

选择(Select)只读(Readonly)是指能够访问一个对象的状态,但并不更改这个状态的操作。

遍历(Throughout)则是以某种方式访问一个对象的所有组成部分的操作。

软件建模时,通常应该明确标明操作的类型。程序设计语言通常提供了不同的语言机制来表示操作的这些类型。

例如,C++程序语言就定义了特定的构造函数和析构函数的表示法,还提供了const关键字来定义选择操作。

属性和操作之间的关系也决定了各操作之间的执行顺序,这个执行顺序则构成了对象的一种使用规则。对象建模时,同样也需要清楚地认识和描述这个规则。

可以说,系统中任何对象都定义或封装了它自己的状态,系统所有的状态都是由其对象所封装的。但对象的状态只是问题的一个方面,还要考虑对象的行为以及状态和行为之间的相互影响。

4.角色和责任

对于任何一个对象来说,它必然要承担部分系统责任,责任(Duty)表示了对象的一种目标以及它在系统中的位置。一个对象必须为所承担的系统责任提供全部服务。

当一个对象承担了较多的系统责任时,就可能需要提供多组不同的方法以履行这些责任。此时,按照责任对对象的这些方法进行分组就显得非常有意义了。这些分组划分了对象的行为空间,也描述了一个对象可能承担的多种系统责任。如果将对象承担的不同的系统责任抽象成不同的角色(Role),那么,我们就得到了一个新的概念。这个概念定义了对象与它的客户之间的契约的一种抽象。

事实上,大多数对象都有可能承担多种不同的系统责任,或者说充当多种不同的系统角色。此时,一个对象所扮演的角色既是动态的,同时也是互斥的。

总之,一个对象的状态和行为共同决定了这个对象可以扮演的角色,这些角色又决定了这个对象它所承担的系统责任。也就是说,角色代表了对对象的责任的一种抽象。

例如,在大多数面向对象程序设计语言中,一个类(对象)可以实现了多个不同的接口,这样这个对象就可能以不同的角色(接口实现)出现在不同的场景当中,以完成不同的任务(或承担不同的职责)。显然,不同接口的责任是根本不同的。对象在不同的场景中所充当的角色也是不同的。进一步说,一个对象充当了哪种角色还可以是动态的,而且可以随时转换。

系统设计过程的实质就是从分析对象所扮演的角色(承担的系统责任)开始,分析并精化这些角色,并为这些角色设计出特定的操作,使这些角色能够通过它所拥有的操作实现它所承担的系统责任。

5.对象标识符

在面向对象系统中,一个对象通常对应了现实世界的某个特定的物理实体或逻辑实体。现实世界中,不同的实体之间显然应该存在不同的区别。为了区别不同的对象,任何一个对象均需要拥有一个对象标识符。

面向对象方法中,并没有明确规定对象标识符的表示方法,但在实际的软件系统中,对象标识符又是一个必须关注和实现的概念。

可以使用多种方法实现对象标识符,如为对象定义的一个属性、使用的程序设计语言表示的变量名等。

例如,可以将对象标识符定义为对象的一个内部属性,这个内部属性不一定具有问题域方面的含义,其主要作用在于将当前对象与其他对象区别开。例如,学生的学号、社会保险号码或居民身份证号等。

在应用程序中,可以使用对象的变量名作为对象标识符,用来区分不同的对象,这实际上是把寻址方法作为对象标识符。

在数据库系统中,还可以使用“主键”作为对象标识符,来区分不同的数据实体(持久对象),这实际上是将对象的某个(或某性)属性作为标识符,这种表示方法具有一定的业务逻辑方面的意义。

总之,对象标识符仅仅作为面向对象方法中的一个概念,通常并不代表程序设计语言中的“标识符”意义或数据库“主键”意义上的标识符。

面向对象系统中,如果不能够正确区分对象的名称对象本身,这将会导致面向对象程序设计中的许多错误。每个对象都需要有一个唯一的标识符,对象标识符在其整个生命周期中都将被保持,即使它的状态发生了某种改变时也是如此。