购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.3 定义属性和行为

现在我们已经了解了一些基本的面向对象的术语。对象是一些可以互相关联的类的实例。类的实例是一个具有自己的数据和行为的特定对象;比如我们面前桌子上的一个橘子就是广义的橘子类的一个实例。

这个橘子有自己的状态,比如生的还是熟的;我们通过特定的属性值来实现对象的状态。橘子也有行为。橘子自身一般是被动的,但其他对象会触发它们的行为,进而引起状态变化。下面我们来深入学习这两个词的含义:状态和行为。

1.3.1 用数据描述对象的状态

我们先从数据开始。数据代表一个特定对象的个体特征,也就是现在的状态。类可以定义一系列属于这个类的对象的共有特征。对于这些特征,任何特定对象都可以拥有不同的数据值。比如,在我们的桌子上放着的3个橘子重量可能各不相同。橘子类可以有一个weight属性表示这个特征,所有橘子类实例都有weight属性,但每个橘子都可以有不同的weight值。不过属性的值并不需要是唯一的,两个橘子也可能重量一样。

属性(attribute)也经常被称为 成员 member )或 特性 property )。有些作者认为这些术语有不同的含义,通常来说属性(attribute)是可以修改的,而特性(property)是只读的。在Python中,特性可以被定义为只读,但它在本质上还是可以修改的,所以只读的概念在Python中没有太大意义。在本书中,我们将这两个词视为同义词。另外,我们在第5章中会讨论property关键字的特殊作用。

在Python中,我们也可以把属性称为 实例变量 。这可以帮助理解属性的原理。属性是属于每个类实例的变量,这些变量可以有不同的值。Python也有其他类型的属性,但我们现在只讨论最常见的这种实例变量。

在我们的水果库存应用中,果农可能希望知道橘子来自哪个果园(orchard),是何时采摘(date_picked)的,以及重量(weight)是多少。他们也许希望跟踪每一个 Basket 中的橘子被存储在哪里(location)。苹果可能有颜色(color)属性,桶可能有不同的尺寸(size)。

有些属性可能是多个类共有的,比如我们可能也想知道是何时采摘的苹果。在这个示例中,我们就随意给类图设置了几个不同的属性,如图1.3所示。

图1.3 带有属性的类图

根据我们设计的具体程序,我们也可以指定每个属性值的类型。在UML中,属性类型通常使用编程语言中通用的名称,比如整数(integer)、浮点数(float)、字符串(string)、字节(byte)或布尔值(Boolean)。然后,它们也可以是列表、树、图等常见的集合类型,甚至是与具体应用相关的其他非通用类型。这是一个设计阶段可能与编程阶段有所重叠的地方。这些基本数据类型和自带的集合类型,在不同的编程语言中可能有所不同。

下面是一个Python版本的带属性类型的类图,如图1.4所示。

图1.4 带属性和属性类型的类图

通常,在设计阶段,我们不需要过度担心数据类型的问题,因为具体的实现细节是在编程阶段确定的。通用的类型名称在设计阶段就够用了,这就是为什么在图1.4中用date表示采摘日期,在实际的Python编程中使用datetime.datetime。如果在设计中需要用到列表类型,Java程序员可以选择LinkedList或ArrayList来实现,而Python程序员(也就是我们)可以选择list类型来实现,通过List[Apple]做类型提示。

到目前为止,在水果库存的示例中,所有的属性都是基本类型的。然而,有一些隐含的属性,我们可以通过关联关系显式说明。对于一个给定的橘子,我们可以用一个basket属性来表示这个橘子所在的篮子,这个属性的类型提示是Basket。

1.3.2 行为就是动作

现在我们知道了如何用数据描述对象的状态,最后一个要学习的术语是行为(behavior)。行为是一个对象可以发生的动作。在某一类对象上可以发生的动作通过这个类的 方法 (method)来表达。在编程层面上,方法就像结构化编程中的函数,但它们可以访问对象的属性,也就是当前对象数据的实例变量。像函数一样,方法也可以接收 参数 并返回表示结果的

参数代表在调用方法时需要 传递 给方法的一系列对象。在实际调用时传给方法的对象案例通常被称为 实参 (argument)。这些对象被绑定到 参数 变量中,然后在方法体中使用,用于执行方法需要完成的任何行为或任务。返回值是任务的结果。在执行方法时可能会造成对象内部状态的变化。

我们已经给我们的“橘子苹果库存管理系统”画了基本的草图,现在继续扩展一下。对于橘子(orange)来说,一个可能的动作是 pick 采摘 )。想一下实现细节, pick 需要做两件事:

· 更新orange的 Basket 属性,记录这个橘子属于某个特定的篮子。

· 更新 Basket Orange 列表属性,记录在这个篮子中有这个橘子。

所以, pick 需要知道它要处理哪个篮子。我们通过将 Basket 作为参数传递给 pick 方法来实现这一点。由于我们的果农同时也卖果汁,所以我们也可以为 Orange 类添加一个 squeeze 榨汁 )方法。当榨汁的时候, squeeze 方法可能会返回获得果汁的数量,同时也需要将 橘子 从它所在的 篮子 中移除。

Basket 类可以有一个 sell 售卖 )动作。当一篮水果被卖掉时,我们的库存系统需要更新一些我们现在还没涉及的对象的数据来记账或者计算利润。或者,我们篮子里的橘子可能还没卖掉就已经坏掉了,因此我们需要添加一个 discard 丢弃 )方法。现在我们将这些方法添加到类图中,如图1.5所示。

图1.5 带属性和方法的类图

通过给各个对象添加属性和方法,我们能够创建一个由交互的对象所组成的 系统 。系统中的每个对象都属于某一个类。这些类指定了对象可以拥有哪些类型的数据以及有哪些可以被调用的方法。每个对象的数据都可能与同一个类中其他对象的状态是不同的。因为对象的状态不同,所以在调用不同对象的方法时可能产生不同的反应。

面向对象的分析和设计就是为了弄清楚有哪些对象以及它们之间应该如何交互。每个类都有责任和要协作的事情。后文描述的原则,就是用来使这些交互过程尽可能简单、直观的。

请注意,销售一篮水果的动作不一定要被放在 Basket 类中。它也可以被放在其他某个负责多个篮子以及篮子位置的类中(图中没有画出)。我们的设计通常都有边界。我们也需要考虑如何把职责分配给不同类的问题。职责分配问题并不总是一下就分得很清楚,这使得我们不得不画多个UML图来比较不同的方案。 AJAZ62VsVOxJ6CHkP3MkR8OeyNqwM0kyC8jUQ2cT4GsvKiuJ5cQROzcWHLPD1QaW

点击中间区域
呼出菜单
上一章
目录
下一章
×