您的位置:首页 > 移动开发 > Objective-C

(转帖)Beginning C# Objects 抓住一个对象

2008-08-23 01:15 218 查看
3.4.2.1 封装(Encapsulation)
封装(Encapsulation)是一个正式术语,表示将对象状态和行为绑到单一逻辑单元中的机制。理论上,对于某个特定学生,我们需要了解的情况,要么直接表现为对象的字段(field),要么间接表现为给出问题的答案或影响对象状态的方法(method)。
封装不是OO语言所独有的,但从某种意义上说,被OO语言完善了。如果你熟悉C语言,会知道一个C struct这样封装数据:
struct employee {
char name[30];
int age;
}
下例中,C语言函数封装了操作逻辑——数据被传入、计算,给出(可选的)返回值:
float average(float x, float y){
return (x + y)/2.0;
}
但是,把数据和行为封装到单一构造,用来表示真实世界实体的抽象模型,这样的概念只有在OO编程语言中得到真正采用。

在C#中,当声明一个用户自定义类型变量,如
Student y;
的时候,并没有在内存中真的创建对象,而只是声明了Student类型的引用变量y。该引用变量可能会指向一个Student对象,但目前还没有;或者说,它的值为null。第1章中已经提到,null是C#关键字,用来表示不存在的对象。
必须通过特别的C#操作符——new——来真正地在内存中创造一个全新的Student对象,和把新对象与引用变量y关联起来,如下所示:
y = new Student();

在幕后,我们实际上是把对象被创建到的物理内存地址与变量y关联起来。
不用过多考虑上例的语句;我们将在第4章谈及构造器(constructor)时说明。
把新创建的对象看作是一个氦气球,如图3-3所示,而引用变量则是抓住气球系绳的手,这样就可以在任何时候访问对象。



图3-3 使用引用变量掌握内存中的对象
引用变量有时被非正式地看作“抓住了”对象,所以我们常使用非正式术语句柄(handle作类似这样的表达:“引用变量y维护一个Student对象的句柄。”
也可以创建新对象,而不把它立即指定给一个引用变量,如下面的代码:
new Student();
但这样的对象如同没有系绳的氦气球:它可能真的存在,但却无法在程序中访问。它将在内存中飘离我们的掌握。
注意,可以把声明引用的变量和真正实体化变量指向的对象这两步合到一起,用一行代码表示:
Student y = new Student();
另一种初始化引用变量的方法是交给它一个已存在的对象:已经为另一个不同的引用变量(“手”)引用(“抓住”)的对象(“氦气球”)。来看一个例子:
// 声明引用变量,实体化第一个Student对象。
Student x = new Student();

// 声明第二个引用变量,但不实体化第二个Student对象。
Student y;
// 向y传递一个“句柄”,该句柄属于y掌握的对象所有
// (x继续掌握它)。现在,我们拥有系着同一个气球的两条“绳”
y = x;
图3-4展示了上述代码的运行结果示意:两条被不同的“手”抓住的“绳”,系着同一个“气球”——两个不同的引用变量指向内存中同一个物理对象。



图3-4 维护同一对象的多个句柄
所以,同一个对象可以同时为多个引用变量所指向,反之,每个引用变量在固定时刻只能掌握/指向个对象。试图掌握新的对象句柄,意味着引用变量必须放弃之前的对象句柄(如果有的话)。
如果一个对象的所有句柄都被放弃,如前所言,对象将不再可被程序访问,如同氦气球的系绳被松开了。继续前面的例子(注意黑体部分的代码和图3-5、3-6和3-7):



图3-5 创建了第二个对象
// 实体化第一个对象。
Student x = new Student();

// 声明第二个引用变量,但不实体化第二个对象。
Student y;
// 把x掌握的对象句柄传递给y
// (x也继续保留对它的掌握)。现在,我们拥有
// 系着同一个气球的两条“绳”。
y = x;

// 声明第三个引用变量,实体化第二个Student对象。
Student z = new Student();
// y放弃第一个Student对象,抓住第二个Student对象。
y = z;



图3-6 传递对象句柄
// 最后,x放弃第一个Student对象,抓住
// 第二个对象。第一个对象丢失了,因为
// 没有任何引用变量维护它的“句柄”!
x = z;



图3-7 第一个对象丢失了
当对象的所有句柄都丢失的时候,看起来似乎对象所占据的内存空间被浪费了。(在C++这样的语言中的确如此。当不再需要某个对象时,在其句柄被全部放弃前,程序员必须显式地回收他占据的内存。内存回收失败是造成C++程序问题的一种痼疾。)在C#(和所有其他.NET语言)中,公共语言运行时CLRcommon language runtime会定时执行垃圾回收操作,自动回收丢失了的对象所占据的内存。在第13章中将回头讨论这个话题。

在初次谈及与Student类相关的attribute和方法时,我们曾提到,该类的一些attribute可以用C#语言的预定义类型来表示,但另外一些(advisor、courseLoad和transcript)则没有做说明应该怎样处理。现在我们来把学到的关于预定义类型的知识付诸使用。
我们把Student类的advisor attribute声明为用户定义类型Professor(见表3-2),而不是简单地用string类型来表示导师姓名。
表3-2 Student类的attribute,第2版
attribute 类型
name string
studentID string
birthdate DateTime
address string
major string
gpa double
advisor Professor
courseLoad ???
transcript ???
通过声明advisorattribute的类型为Professor——即,使advisor attribute成为引用变量——使得Student对象得到一个真实Professor对象的句柄,该对象表示学生的导师。对于courseLoad和transcript类型,稍后再作理会。
现在轮到Professor类了,表3-3列出了该类的attribute定义。
表3-3 Professor 类的attribute
attribute 类型
name string
employeeID string
birthdate DateTime
address string
worksFor string (或Department)
studentAdvisee Student
teachingAssignments ???
将Professor类的studentAdvisee attribute声明为Student类型——即,使studentAdvisee attribute成为引用变量——我们让Professor对象能够抓住/指向一个Student对象,该对象表示教授指导的学生。teachingAssignments attribute类型放到后面讨论。
Professor类的方法大概如下所示:
1. TransferToDepartment
2. AdviseStudent
3. AgreeToTeachCourse
4. AssignGrades
关于Professor类,需要说明几点:
l 一位教授应该会同时指导数名学生,而类似studentAdvisee这样的attribute只能指向单个Student对象,不十分管用。我们将在第6章谈到群集(collections)时讨论处理这种情况的技术,它对于定义Professor类的teachingAssignments attribute,Student类的courseLoad和transcript attribute非常有用。
l worksFor attribute表示教授任职的院系。可以选择使用简单的string类型来表示院系名,如“MATH(数学系)”;或将其定义为指向一个Department对象的引用变量,特别地,Department对象代表“真实世界”中的数学系。当然,这需要定义一个名为Department的新类,以及其attribute和方法。如你将在本书第二部分所看到的那样,是否应该创造用来表示真实世界概念/抽象模型的用户定义类型/类,并非简单的决定
当创建一个类,如Student或Professor,该类的一个或多个attribute指向另外的对象,则我们是在应用一种被称做“合成(composition)”的OO技术。对象互相内嵌的层次是无限制的,合成技术让我们可以对非常复杂的真实世界概念建模。多数“有趣的”类应用了合成技术。
在使用合成技术时,看起来就像把一个对象嵌入到另一个对象中,如图3-8所示。



图3-8 对象“内嵌”示意
真正的对象内嵌(即,在一个对象中声明另一对象)在一些OO编程语言中是可能的。从OO应用程序的立场来看,如果对象A只是为了让对象B更完整而存在,且自身没有必要独立存在的需要,则对象内嵌也有其意义。
l 例如,想想你的大脑,把它看作只在身体(另一对象)“上下文”中具有存在意义的对象。
l 举个跟SRS系统有关的对象内嵌例子。考虑跟踪学生某门课程学习表现的成绩表。如果定义一个GradeBook类,以GradeBook对象作为其attribute——每个Course对象对应一个GradeBook对象——则对于每个GradeBook对象,完全存在于相应Course对象的上下文中,是有道理的。没有其他对象需要和GradeBook对象直接沟通;如果Student对象想问Course对象自己的得分,Course可以询问内嵌的GradeBook对象,然后再转告Student对象。
然而,我们常常遇到这种情形——如Student和Professor对象——对象A需要引用对象B,对象B也需要引用对象A,且两个对象均要能单独响应应用程序请求。这种状况下,句柄就成了救星。现实中,我们并不把整个对象作为attribute储存在另一对象中,而只保存对象引用。对象A的一个attribute被定义为对象B的引用,这两个对象就在内存中分别单独存在,而且在需要互动时有方便的途径可以找到对方。
把你自己想象为一个对象,而移动电话号码则是你的对象引用。其他人——“对象”——在需要的时候能够通过电话号码找到你并和你通话,即便他们不知道你身处何方。
使用句柄时的内存分配状态示意如图3-9所示。



图3-9 对象分别独立存在于内存中,并持有对方的句柄
通过这种方法,每个对象只在内存中被创建一次;在需要的时候,通过对象句柄/引用,Student对象懂得如何去找到advisor(Professor)对象并与之沟通,反之亦然。
把Student对象的advisor attribute定义为对Professor对象的引用,而不单是储存导师姓名(string类型),这样做得到什么好处呢?
首先,可以在需要时询问Professor对象的名称(使用的技术将在第4章讨论)。为什么这样做很重要?因为它可以避免数据冗余和数据丢失的潜在可能。
l 假使Professor对象的名称因为某种原因改变了,名称也只是保存在一个地方:它被封装为Professor对象的attribute,隶属关系很清晰。
l 如果把Professor的名字既作为Professor对象的string类型attribute,又作为Student对象的string类型attribute来保存,就得在名字改变时记得去更新两个对象attribute值(甚至是三个、四个或更多,因为一位教授可能指导许多学生)。如果忘记更新,则Professor的名字将不再保持同步状态。
其次,同样重要的是,通过Student对象的advisor attribute去持有Professor对象的句柄,Student对象也能经由Professor类所定义的方法去请求Professor对象提供的其他服务。例如,Student对象可以询问其导师(Professor)对象办公室的位置所在,或讲授哪些课程,以便登记上课。
从实现角度看,使用对象句柄的另一好处是能够减少内存开支。保存一个对象的引用(或称对象的内存地址)只需要4个字节(在32位计算机上)或8个字节(在64位计算机上)的内存空间,而保存整个对象则将耗费许多字节。如果在所有需要的地方都弄一个对象拷贝,很快就会把应用程序能用的内存全部耗尽

在本章中,你学到了
l 对象可以看作是填充了内容的模板。
l 如同可以将变量声明为int, double和bool等简单预定义类型,也可以声明变量为用户定义类型,如Student和Professor。
l 当创建新对象(该过程被称作实体化)时,典型做法是在一个引用变量中保存该对象的引用。使用这个“句柄”来和对象沟通。
l 可以把类A的attribute定义为类B的句柄。这样,对象就能正确封装自身信息,但仍可在必要时互相联络和共享信息。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: