第1章 OO大智慧
1.1 对象的旅行
本节将介绍以下内容:
·面向对象的基本概念
·.NET基本概念评述
·通用类型系统
1.1.1 引言
提起面向对象,每个程序设计者[0]都有自己的理解,有的深入肌理,有的剑走偏锋。但是无论所长,几个基本的概念总会得到大家的重视,它们是:类、对象、继承、封装和多态。很对,差不多就是这些元素构成了面向对象设计开发的基本逻辑,成为数以千万计程序设计者不懈努力去深入理解和实践的根本。而实际上,理解面向对象一个重要的方法就是以实际的生活来类比对象世界,对象世界的逻辑和我们生活的逻辑形成对比的时候,这种体验将会更有亲切感,深入程度自然也就不同以往。
本节就从对象这一最基本元素开始,进行一次深度的对象旅行,把.NET面向对象世界中的主角来一次遍历式曝光。把对象的世界和人类的世界进行一些深度类比,以人类的角度戏说对象,同时也以对象的逻辑反思人类。究竟这种旅程,会有什么样的洞悉,且看本文的演义[0]。
对象和人,两个世界,一样情怀。
1.1.2 出生
对象就像个体的人,生而入世,死而离世。
我们的故事就从对象之生开始吧。首先,看看一个对象是如何出生的:
Person aPerson = new Person("小王", 27);
那么一个人又是如何出生呢?每个婴儿随着一声啼哭来到这个世界,鼻子是鼻子、嘴巴是嘴巴,已经成为一个活生生的独立的个体。而母亲的怀胎十月是人在母体内的成长过程,母亲为胎儿提供了所有的养分和舒适的环境,这个过程就是一次实实在在的生物化构造。同样的道理,对象的出生,也是一次完整的构造过程:首先会在内存中分配一定的存储空间;然后初始化其附加成员,就像给人取个具有标识作用的姓名一样;最后,再调用构造函数执行初始化,这样一个对象实体就完成了其出生的过程,例如上例中我们为aPerson对象初始化了姓名和年龄。
正如人出生之时,一身赤裸没有任何的附加品,其余的一切将随需而生,生不带来就是这个意思。对象的出生也只是完成了对必要字段的初始化操作,其他数据要通过后面的操作来完成。例如对属性赋值,通过方法获取必要的信息等。
1.1.3 旅程
婴儿一出世,由it成为he or she,就意味着从此融入了复杂的社会关系,经历一次在人类伦理与社会规则的双重标准中生活,开始了为人的旅程。同理,对象也一样。
作为个体的人,首先是有类型之分的,农民、工人、学者、公务员等,所形成的社会规则就是农民在田间务农,工人在工厂生产,学者探讨知识,公务员管理国家。[0]
对象也一样是有类型的,例如整型、字符型等。当然,分类的标准不同,产生的类别也就不同。但是常见的分类就是值类型和引用类型两种。其依据是对象在运行时在内存中的位置,值类型位于线程的堆栈,而引用类型位于托管堆。正如农民可以进城务工,工人也可以回乡务农,值类型和引用类型的角色也会发生转变,这个过程在面向对象中称为装箱与拆箱。这一点倒是与刚刚的例子很贴切,农民进城,工人回乡,不都得把行李装进箱子里折腾嘛。
作为人,我们都是有属性的,例如你的名字、年龄、籍贯等,用来描述你的状态信息,同时每个人也用不同的行为来操作自己的属性,实现了与外界的交互。对象的字段、属性就是我们自己的标签,而方法就是操作这些标签的行为。人的名字来自于长辈,是每个人在出生之时构造的,这和对象产生时给字段赋值一样。但是每个人都有随时更名的权力,这种操作名称的行为,我们称之为方法。在面向对象中,可以像这样来完成:
aPerson.ChangeName("Apple Boy");
所以,对象的旅行过程,在某种程度上就是外界通过方法与对象交互,从而达到改变对象状态信息的过程,这也和人的生存之道暗合。
人与人之间通过语言交流。人一出生,就必然和这个世界的其他人进行沟通,形成种种相互的关系,[0]融入这个完整的社会群体。在对象的世界里,你得绝对相信对象之间也是相互关联的,不同的对象之间发生着不同的交互性操作,那么对象的交互是通过什么方式呢?对象的交互方式被记录在一本称为“设计模式”的魔法书中,当你不解以什么样的方式建立对象与对象之间的关系时,学习前人的经验,往往是最好的选择。[0]
下面,我们简要地分析一下对象到底旅行在什么样的世界里?
对象的生存环境是CLR,而人的生存环境是社会。CLR提供了对象赖以生存的托管环境,制定一系列的规则,称之为语法,例如类型、继承、多态、垃圾回收等,在对象世界里建立了真正的法制秩序;而社会提供了人行走江湖的秩序,例如法律、规范、道德等,帮助我们制约个体,维护社会。
人类社会就是系统架构,也是分层的。上层建筑代表政治和思想,通过社会契约和法律规范为经济基础服务,在对象世界中,这被称为接口。面向接口的编程就是以接口方式来抽象变化,从而形成体系。正如人类以法律手段来维系社会体系的运作和秩序一样。
由此可见,对象的旅行就是这样一个过程,在一定的约定与规则下,通过方法进行彼此的交互操作,从而达到改变本身状态的目的。从最简单的方式理解实际情况,这些体会与人的旅程如此接近,给我们的启示更加感同身受。
1.1.4 插曲
接下来,我们以与人类世界的诸多相似之处,来进一步阐释对象世界的几个最熟悉的概念。
关于继承。人的社会中,继承一般发生在有血缘关系的族群中。最直接的例子一般是,儿子继承父亲,包括姓氏、基因、财产和一切可以遗留的东西。但并不代表可以继承所有,因为父亲隐私的那一部分属于父亲独有,不可继承。当然,也可能是继承于族群的其他人,视实情而定。而在面向对象中,继承无处不在,子类继承父类,以访问权限来实现不同的控制规则,称为访问级别,如表1-1所示。
表1-1 访问修饰符
这些规则可以以公司的体制来举例说明,将公司职权的层级与面向对象的访问权限层级做类比,应该是这样:
·public,具有最高的访问权限,就像是公司的董事会具有最高的决策权与管理权,因此public开放性最大,不管是否同一个程序集或者不管是否继承,都可以访问。
·protected,类似于公司业务部门经理的职责,具有对本部门的直接管辖权,在面向对象中就体现为子类继承这种纵向关系的访问约定,也就是只要继承了该类,则其对象就有访问父类的权限,而不管这两个具有继承关系的类是否在同一个程序集中。
·internal,具有类比意义的就是internal类似于公司的职能部门的职责,不管是否具有上下级的隶属关系,人力资源部都能管辖所有其他部门的员工考勤。这是一种横向的职责关系,在面向对象中用来表示同一程序集的访问权限,只要是隶属于同一程序集,对象即可访问其属性,而不管是否存在隶属关系。
·protected internal,可以看做是protected internal的并集,就像公司中掌管职能部门的副总经理,从横向到纵向都有管理权。
·private,具有最低的访问权限,就像公司的一般员工,管好自己就行了。因此,对应于面向对象的开放性最小。
另外,对象中继承的目的是提高软件复用,而人类中的继承,不也是现实中的复用吗?
而关于多态,人的世界中,我们常常在不同的环境中表现为不同的角色,并且遵守不同的规则。例如在学校我们是学生,回到家里是儿女,而在车上又是乘客,同一个人在不同的情况下,代表了不同的身份,在家里你可以撒娇但是在学校你不可以,在学校你可以打球但在车上你不可以。所以这种身份的不同,带来的是规则的差异。在面向对象中,我们该如何表达这种复杂的人类社会学呢?
interface IPerson { string Name { get; set; } Int32 Age { get; set; } void DoWork(); } class PersonAtHome : IPerson { } class PersonAtSchool : IPerson { } class PersonOnBus : IPerson { }
显然,我们让不同角色的Person继承同一个接口:IPerson。然后将不同的实现交给不同角色的人自行负责,不同的是PersonAtHome在实现时可能是CanBeSpoil(),而PersonOnBus可能是BuyTicket()。不同的角色实现不同的规则,也就是接口协定。在使用上的规则是这个样子:
IPerson aPerson = new PersonAtHome(); aPerson.DoWork();
另一个角色又是这个样子:
IPerson bPerson = new PersonOnBus(); bPerson.DoWork();
由此带来的好处是显而易见的,我们以IPerson代表了不同角色的人,在不同的情况下实现了不同的操作,而把决定权交给系统自行处理。这就是多态的魅力,其乐无穷中,带来的是面向对象中最为重要的特性体验。记住,很重要的一点是,DoWork在不同的实现类中体现为同一命名,不同的只是实现的内部逻辑。
这和我们的规则多么一致呀!
当然,有必要补充的是对象中的多态主要包括以下两种情况:
·接口实现多态,就像上例所示。
·抽象类实现多态,就是以抽象类来实现。
其细节我们将在1.4节“多态的艺术”中加以详细讨论。
由此可见,以我们自己的角度来阐释技术问题,有时候会有意想不到的收获,否则你将被淹没在诸如“为什么以这种方式来实现复用”的叫喊中不能自拔。换一个角度,眼界与思路都会更加开阔。
1.1.5 消亡
对象和人,有生必然有死。在对象的世界里,它的生命是由GC控制的,而在人的世界里我们把GC称为自然规律。进入死循环的对象,是违反规则的,必然无法逃脱被Kill的命运[0],就如同没有长生不死的人一样。[0]
在这一部分,我们首先观察对象之死,以此反思和体味人类入世的哲学,两者相比较,也会给我们更多关于自己的启示。对象的生命周期由GC控制,其规则大概是这样:GC管理所有的托管堆对象,当内存回收执行时,GC检查托管堆中不再被使用的对象,并执行内存回收操作。不被应用程序使用的对象,指的是对象没有任何引用。关于如何回收、回收的时刻,以及遍历可回收对象的算法,是较为复杂的问题,我们将在6.3节“垃圾回收”中进行深度探讨。不过,这个回收的过程,同样使我们感慨。大自然就是那个看不见的GC,造物而又终将万物回收,无法改变。我们所能做到的是,将生命的周期拓宽、延长、书写得更加精彩。[0]
1.1.6 结论
程序世界其实和人类世界有很多相似的地方,本节就以这种类比的方式来诠释这两个世界的主角:对象和人。以演化推进的手法来描述面向对象程序世界的主角对象由生而死的全过程,好似复杂的人生。而其实,人也可以是简单的。这是一种相互的较量,也是一种相互的借鉴。