对象就是 OOP 应用程序的一个组成部分。这个组成部件封装了部分应用程序,这部分程序可以是一个过程、一些数据或一些更抽象的实体。
简单地说,对象非常类似于本书前面讨论的结构类型,包含变量成员和函数类型。它所包含的变量组成了存储在对象中的数据,其中包含的函数可以访问对象的功能。略为复杂的对象可能不包含任何数据,而只包含函数,表示一个过程。例如,可以使用表示打印机的对象,其中的函数可以控制打印机(允许打印文档、测试页等)。
C#中的对象是从类型中创建的,就像前面的变量一样。对象的类型在 OOP 中有一个特殊的名称:类。可以使用类的定义实例化对象,这表示创建该类的一个命名实例。“类的实例” 和对象含义相同,但 “类” 和 “对象” 是完全不同的概念。
术语 “类” 和 “对象” 常常混淆,从一开始就正确区分它们是非常重要的,使用前面的赛车示例有助于区分这两个术语。在这个示例中,类是指汽车的模版,或者用于构建汽车的规则。汽车本身是这些规划的实例,所以可以看作对象。
本行将使用统一建模语言(Unified Modeling Language,UML)语法研究类和对象。UML 是为应用程序建模而设计的,从组成应用程序的对象,到它们执行的操作,到我们希望有用的用例,应有尽有。这里只使用这个语言的基本部分,在使用它们的过程中进行解释,但不考虑比较复杂的部分,因为 UML 是一个很专业的主题,有很多图书专门介绍它。
VS有一个类查看器,它是一个功能很强大的工具,可用于以类似的方式显示类。但为了简单起见,本章的图是手工绘制的。
图 8-1
是打印机类 Printer
的 UML 表示方法。类名显示在这个框的顶部(后面将讨论下面两个区域)。
图 8-2
是这个 Printer 类的一个实例 myPrinter 的 UML 表示方法。
在顶部,首先显示实例名,其后面是类名。这两个名称用一个冒号分隔。
1. 属性和字段
可以通过属性和字段访问对象中包含的数据。这个对象数据可以用于区分不同的对象,因为同一个类的不同对象在属性和字段中存储了不同的值。
包含在对象中的不同数据构成了对象的状态。假定一个对象类表示一杯咖啡,称为 CupOfCoffee。在实例化这个类(即创建这个类的对象)时,必须提供对类具有意义的状态。此时可以使用属性和字段,让代码能通过该对象设置要使用的咖啡品牌,咖啡中是否加牛奶或方糖,咖啡是否即溶等。于是,给定的这杯咖啡对象就有了指定的状态,例如,加牛奶和两块方糖的哥伦比亚滴滤咖啡。
字段和属性都可以键入,所以可以把信息存储在字段和属性中,作为 string
值、int
值等。但是,属性和字段是不同的,因为属性不提供对数据的直接访问。对象能让用户不考虑数据的细节,不需要在属性中用一对一的方式表示。如果在 CupOfCoffee
实例中使用一个字段表示方糖的数量,用户就可以用该字段中放置自己喜欢的值,其取值范围仅由存储该信息的类型来限制。例如,如果使用 int
来存储这个数据,用户就可以使用 -214783648~2147483647 之间的任意值,如第 3 章所述。显然,并不是所有的值都有意义,尤其是负值,一些较大的正值将需要非常大的咖啡杯。但如果使用一个属性来表示,久可以限制这个值,例如介于 0~2 之间的一个数字。
一般情况下,在访问状态时最好提供属性,而不是字段,因为这样可以更好地控制各种行为,这个选择不会影响使用对象实例的代码,因为使用属性和字段的语法是相同的。
对属性的读写访问也可以由对象来明确定义。某些属性是只读的,只能查看它们的值,而不能改变它们(至少不能直接改变)。这常常是同时读取几个状态的一个有效技巧。CupOfCoffee 类有一个只读属性 Description
,在请求它时,就返回一个字符串,表示该类的一个实例的状态(例如前面给出的字符串)。也可以通过查看几个属性,把相同的数据组合起来,但这样的属性可以节省时间和精力。还可以有只写的属性,其操作方式是类似的。
除了对属性的读/写访问外,还可以为字段和属性指定另一种访问权限,称为可访问性。可访问性确定了什么代码可以访问这些成员,它们是可用于所有的代码(公共),还是只能用于类中的代码(私有),或者更复杂的模式(详见本章后面的内容)。常见的情况是把字段设置为私有,通过公共属性访问它们。这样,类中的代码就可以直接访问存储在字段中的数据,而公共属性禁止外部用户访问这些数据,以防他们在其中放置无效的内容。公共成员是类公开的成员。
要更清晰地阐明这个问题,可以把可访问性与变量的作用域等同起来。例如,私有字段和属性可以看成是拥有它们的对象的局部成员,而公共字段和属性的作用域也包括对象以外的代码。
在类的 UML 表示方法中,用第二部分显示属性和字段,如图 8-3 所示
。
这是 CupOfCoffee
类的表示方式,前面为它定义了 5 个成员(属性或字段,在 UML 中,它们没有区别)。每个成员都包含下述信息:
● 可访问性: +
号表示公共成员, -
号表示私有成员。但一般情况下,本章的图中不显示私有成员,因为这些信息是类内部的信息。至于读/写访问,则不提供任何信息。
● 成员名
● 成员的类型。
冒号用于分隔成员名和类型。
2. 方法
“方法” 这个术语用于表示对象中的函数。这些函数调用的方式与其他函数相同,使用返回值和参数的方式也相同(详见第 6 章)。
方法用于访问对象的功能。与字段和属性一样,方法也可以是公共的或私有的,按照需要限制外部代码的访问。它们通常使用对象状态影响它们的操作,在需要时访问私有成员,如私有字段。例如,CupOfCoffee
类定义了一个方法 AddSugar()
,该方法对递增方糖数提供了比设置相应的 Sugar
属性更易读的语法。
在 UML 的类框中,方法显示在第三部分,如图 8-4 所示。
其语法类似于字段和属性,但最后显示的类型是返回类型,在这一部分,还显示了方法的参数。在 UML 中,每个参数都带有下述标识符之一:in
、out
或 inout
。它们用于表示数据流的方向,其中 out
和 inout
大致对应于第 6 章讨论的 C#关键字 out
和 ref
。in
大致对应于 C#中不使用这两个关键字的情形(默认情形)。