同一程序中可以有同一类型的多个对象。例如,在用水模型的程序中可能有多个水龙头对象,管道对象,装置对象以及用户对象。 同一类型的对象我们认为是同一个类的实例。同一个类的实例具有同样的方法,同样的实例变量(实例变量的值可能不同),并且共享同一份类的定义。
从这一点上,类和C中的结构体类似,都是定义了一种类型。例如,下面的声明
定义了struct key这种类型。一旦被定义,就可以创建struct key的实例:
上面的第一段代码并没有创建程序能实际使用的变量,而是创建了结构体类型的一个模板。第二段代码才是创建该类型的实例并为它分配内存。
类似的,类的定义只是创建了某种类型的对象的一个模板,这个模板可以被用来创建多个同一类型的对象—类的实例 。例如,程序中只会有一份水龙头类的定义。通过这个定义,程序能够创建很多它需要的水龙头对象并为之分配内存。
和结构体的定义一样,类的定义也包括数据元素(实例变量)。每个该类的对象实例都会包含这些数据元素,并为它们分配内存。这些数据元素中保存着各个对象实例独有的数据。
然而,和结构体定义不同的是,类定义同时也包括描述类行为的方法。对象的所属的类随着该对象拥有的方法的不同而不同。两个对象拥有同样的数据元素但是不同的方法则不会属于同一个类。
本节包含如下内容:
模块化
复用性
模块化
对于C程序员来说,模块往往意味着源代码的一个文件。把一个大型的程序(甚至不用很大)的代码拆分到不同的文件中是一种很方便的管理方式。每个文件都可以单独的编译,然后链接在一起就是最后的程序。使用static标识符能够使符号的作用域仅限于该符号所在的文件从而减小模块间的耦合性。
然而,这种模块的划分是由文件系统决定的。它更象是源代码的容器而不是语言上的逻辑单位。容器内部的实现对于每个程序员都是可见的。您可以将逻辑上相关的代码组织在一个文件中,但您也可以不这么做,完全没有限制。文件就象一个橱柜的抽屉,您可以一个抽屉放袜子,另一个放内衣,等等,或者您也可以以另外一种分类方式来存放,或者简单的全都放在一起。
对象的方法:和实例变量一样,我们很容易认为方法是对象的一部分。如图 3-1所示,方法围绕在对象实例变量的周围。然而,在内存中并不是如此。每个对象的实例变量都会占据一块内存,但无论创建了多少个该类的对象实例,同一个类的对象的方法在内存中只会存在一份,。
面向对象编程语言既支持利用文件来划分模块,也支持通过类从逻辑上划分模块。通常,这两种模块划分方法是一致的,因为每个类都被定义在不同的文件中。
例如在Objective-C中,管道类的定义源文件中可能包含了阀门类和管道类交互的部分,这儿的管道类是按文件划分的模块,而阀门类的定义被划分到好几个文件中,但在整个程序结构中,阀门类仍是一个模块单元——按逻辑划分的模块——无论它被定义到多少个文件中。
关于类定义和逻辑模块的之间的更多细节将在 “抽象机制”一节中进行讨论。
复用性
面向对象编程的一个主要目的就是让您的代码尽可能的被重用——在不同的情景下和不同的应用程序中——从而避免重复实现。代码的可复用性与很多因素有关,包括:
代码的可靠性和bug的多少
文档的清晰程度
程序接口是否简单直接
代码的性能如何
代码的功能如何
显然,这些因素不仅仅作用在对象模型中,它们可以用来衡量任何代码的复用性—无论是标准的C的函数还是类的定义。例如,高效的和有着良好的文档的函数的可复用性要大于那些不可靠的和没有文档的函数。
虽然如此,我们仍然可以作一个比较来说明类的可复用性要好于函数。虽然有多种方式可以增强函数的可复用性—例如将数据作为参数传递给函数而不是使用全局变量,然而,仍然只有一小部分函数能被其它的应用程序复用。函数的可复用性的局限主要体现在三个方面:
函数名是全局的;每个函数必须有一个唯一的名字(除了被声明为static的函数)。这使得开发一个复杂的系统时很难依赖那些函数库。编程接口因此变得难于学习和异常庞大。
另一方面,类可以很容易的共享编程接口。不同的类可以有同样的方法名,这一命名机制使得许多功能性的实现可以共用一个很小的,易于理解的接口。
函数只能挨个从函数库中选择,程序员需要负责从函数库中选取自己需要的函数。
与之相反,类是作为一组功能整体出现的而不是单独的函数和实例变量。类提供的是集成的服务,所以面向对象的程序员不用为集成库和自己的实现方案而烦恼。
函数通常和特定的数据结构绑定在一起。数据和函数的互操作不可避免的使得数据接口也成为接口的一部分,因此函数仅仅对那些使用和函数参数同样数据结构的用户有用。
类的数据都已得到很好的封装,所以不会有同样的问题。这也是类的可复用性比函数更好的一个最主要的原因。
对象的数据对于程序的其它部分来说是不可见的,因而对象的方法不用进行数据完整性的检查。数据不可能会有逻辑错误或者不合理的状态。因此,对象的数据远比传递给函数的参数可靠,从而可复用的对象方法也比函数更容易设计一些。
此外,由于类的数据是封装在类的实现中的,所以类可以重新实现它的数据结构而不会影响类的接口。使用该类的程序不用修改任何代码就可以使用该类的更新版本。
从这一点上,类和C中的结构体类似,都是定义了一种类型。例如,下面的声明
struct key {
char *word;
int count;
};
定义了struct key这种类型。一旦被定义,就可以创建struct key的实例:
struct key a, b, c, d;
struct key *p = malloc(sizeof(struct key) * MAXITEMS);
上面的第一段代码并没有创建程序能实际使用的变量,而是创建了结构体类型的一个模板。第二段代码才是创建该类型的实例并为它分配内存。
类似的,类的定义只是创建了某种类型的对象的一个模板,这个模板可以被用来创建多个同一类型的对象—类的实例 。例如,程序中只会有一份水龙头类的定义。通过这个定义,程序能够创建很多它需要的水龙头对象并为之分配内存。
和结构体的定义一样,类的定义也包括数据元素(实例变量)。每个该类的对象实例都会包含这些数据元素,并为它们分配内存。这些数据元素中保存着各个对象实例独有的数据。
然而,和结构体定义不同的是,类定义同时也包括描述类行为的方法。对象的所属的类随着该对象拥有的方法的不同而不同。两个对象拥有同样的数据元素但是不同的方法则不会属于同一个类。
本节包含如下内容:
模块化
复用性
模块化
对于C程序员来说,模块往往意味着源代码的一个文件。把一个大型的程序(甚至不用很大)的代码拆分到不同的文件中是一种很方便的管理方式。每个文件都可以单独的编译,然后链接在一起就是最后的程序。使用static标识符能够使符号的作用域仅限于该符号所在的文件从而减小模块间的耦合性。
然而,这种模块的划分是由文件系统决定的。它更象是源代码的容器而不是语言上的逻辑单位。容器内部的实现对于每个程序员都是可见的。您可以将逻辑上相关的代码组织在一个文件中,但您也可以不这么做,完全没有限制。文件就象一个橱柜的抽屉,您可以一个抽屉放袜子,另一个放内衣,等等,或者您也可以以另外一种分类方式来存放,或者简单的全都放在一起。
对象的方法:和实例变量一样,我们很容易认为方法是对象的一部分。如图 3-1所示,方法围绕在对象实例变量的周围。然而,在内存中并不是如此。每个对象的实例变量都会占据一块内存,但无论创建了多少个该类的对象实例,同一个类的对象的方法在内存中只会存在一份,。
面向对象编程语言既支持利用文件来划分模块,也支持通过类从逻辑上划分模块。通常,这两种模块划分方法是一致的,因为每个类都被定义在不同的文件中。
例如在Objective-C中,管道类的定义源文件中可能包含了阀门类和管道类交互的部分,这儿的管道类是按文件划分的模块,而阀门类的定义被划分到好几个文件中,但在整个程序结构中,阀门类仍是一个模块单元——按逻辑划分的模块——无论它被定义到多少个文件中。
关于类定义和逻辑模块的之间的更多细节将在 “抽象机制”一节中进行讨论。
复用性
面向对象编程的一个主要目的就是让您的代码尽可能的被重用——在不同的情景下和不同的应用程序中——从而避免重复实现。代码的可复用性与很多因素有关,包括:
代码的可靠性和bug的多少
文档的清晰程度
程序接口是否简单直接
代码的性能如何
代码的功能如何
显然,这些因素不仅仅作用在对象模型中,它们可以用来衡量任何代码的复用性—无论是标准的C的函数还是类的定义。例如,高效的和有着良好的文档的函数的可复用性要大于那些不可靠的和没有文档的函数。
虽然如此,我们仍然可以作一个比较来说明类的可复用性要好于函数。虽然有多种方式可以增强函数的可复用性—例如将数据作为参数传递给函数而不是使用全局变量,然而,仍然只有一小部分函数能被其它的应用程序复用。函数的可复用性的局限主要体现在三个方面:
函数名是全局的;每个函数必须有一个唯一的名字(除了被声明为static的函数)。这使得开发一个复杂的系统时很难依赖那些函数库。编程接口因此变得难于学习和异常庞大。
另一方面,类可以很容易的共享编程接口。不同的类可以有同样的方法名,这一命名机制使得许多功能性的实现可以共用一个很小的,易于理解的接口。
函数只能挨个从函数库中选择,程序员需要负责从函数库中选取自己需要的函数。
与之相反,类是作为一组功能整体出现的而不是单独的函数和实例变量。类提供的是集成的服务,所以面向对象的程序员不用为集成库和自己的实现方案而烦恼。
函数通常和特定的数据结构绑定在一起。数据和函数的互操作不可避免的使得数据接口也成为接口的一部分,因此函数仅仅对那些使用和函数参数同样数据结构的用户有用。
类的数据都已得到很好的封装,所以不会有同样的问题。这也是类的可复用性比函数更好的一个最主要的原因。
对象的数据对于程序的其它部分来说是不可见的,因而对象的方法不用进行数据完整性的检查。数据不可能会有逻辑错误或者不合理的状态。因此,对象的数据远比传递给函数的参数可靠,从而可复用的对象方法也比函数更容易设计一些。
此外,由于类的数据是封装在类的实现中的,所以类可以重新实现它的数据结构而不会影响类的接口。使用该类的程序不用修改任何代码就可以使用该类的更新版本。