Delphi面向对象学习随笔四:继承与封装 作者:巴哈姆特 (转载请注明出处并保持完整) 在讨论类的封装前,我想先说说“继承”和“包含”的区别。 继承(是一个(is a ...)): 我在看很多资料的时候,讲到类的继承时,很多资料都会提到:“选一个合适的类做为新类的父类可以有效的提高代码的重用程度,从而减少很多重复的工作量。” 的确,上面的话很有道理,合理的选择父类是可以减少重复的工作,但是,这里存在一个逻辑性的问题:即、新的子类“是一个(种)”父类,这么说或许有些绕口,举个例子:例如动物类是生物类的子类,那么我们也可以这样说:“动物是一种生物。”这样说,显然在逻辑上并没有矛盾的地方。 那么,我们来看下一个例子: type TEmployee = class(TStrings) ... public property EmployeeName: string ...; //员工姓名 property EmployeeAge: Word ...; //年龄 .... property Strings.....; //考勤记录 end; 这是我曾经看过的一个类,描述一员工的信息的,包括其考勤记录。首先这个类是没有语法和语义错误的,而且工作起来也正常,但是它在“继承”这个逻辑概念上却有问题,那就是“员工/职员 是 一个列表,”这样说显然不合情理。 我曾经问过作者这样设计的意图,他回答只是“这样简单,可以运行就得了,管那么多做什么?”但是,我认为,这样是错误的,因为在逻辑上说不通。 包含(有一个(has a ...)) 包含其实就是指一个类包含另一个类的实例,比如Delphi中的TCombobox的Items属性。 上面的类,我们可以这样改: type TEmployee = class(TObject) ... public property EmployeeName: string ...; ... property Attendances: TStrings ...; //考勤记录 end; 这样看看是不是好多了呢,而且在逻辑上就从“员工是一个列表”变成了“员工有一个考勤列表”。 类的封装: 在决定编写一个新类前,我们首先应该仔细考虑这个类是用来干吗的,而不是兴奋的开始写代码。一个良好的类的接口(属性或方法,和interface是两个概念,以下统称接口,懒得打那么多字,^_^ 或许后面会说interface的概念)应该是具有一致性的。 假如有一个类,他的一部分接口用来操作堆栈,一部分接口用来从数据库中提取数据,还有一部分接口用来控制打印走纸,那么,这个类看上去真的比较乱,因为用户不知道你这个类到底是为什么编写的,这样的类也就不能称之为类了。看上去更像是把一堆风马牛不相及的函数死拉硬拽到一起“凑份子”凑出来的“累”。 一个封装良好的类,应该有一个一致性的接口,也就是说,类提供给外界使用的所有接口都应该是有关联的,上面的类其实我们可以写成三个类比较好:一个类用来操作数据库、一个类用来操作堆栈、一个类用来控制打印机走纸。 这样设计的好处是能让人一看就清楚的知道,你这个类到底是围绕什么来工作的,就像我们写作文也必须有一个中心思想一样,如果没有中心思想,那么这篇作文也就不叫作文了,而是流水帐了。 另外,友情提示一下,在任何时候,都不要有“写一个万能类”的想法。 隐藏信息: 在编写类的时候,我们应该非常清楚的知道,这个类有哪些信息是不需要外界访问的,就像司机开汽车,司机只需要知道怎么弄方向盘,怎么踩离合器,怎么刹车和提速就可以了。而有关汽车内部发动机的工作原理,司机并不需要知道。 当我们在拿到一个类的CODE的时候,除非你是为了学习,否则你应该忍住看私有域所有实现代码的冲动。虽然现在提倡开源,但是我想开源的目的是为了提高相互交流,而并不是为了让你可以把原本私有的、外界无法访问的接口对外开放。如果你把原先外界无法访问的域信息全部对外公开了,那么你这个类存在就无任何意义了。 属性: 类与类之间传递数据,我推荐使用属性property而不是直接用变量来传递。 例如:TControl类的Left、Top、Width、Height四个属性,如果它是以变量的方式来提供给外部直接使用的话,我们设想一下一个这样的需求:假如我们要改变一下组件宽度,并且改变宽度后要在组件最右边绘制一张位图,那么当外界改变了组件宽度时,由于这个变量是直接供外界读写的,它在被写而发生改变的时候,组件无法得到通知,因此也就无法实现重画位图的需求了。 而在属性定义的时候,你必须指定一个read限定符和一个write限定符(read 或 write 都可选 但你至少必须指定一个限定符),当你为一个属性的write限定符指定了一个方法(Delphi中必须是一个过程)时,当你执行如:TControl.Width:= 0;这样的代码时,编译器会自动为你调用write指定的方法,这样我们就可以在属性发生改变的时候让类去执行一些有关的操作,换言之就是类可以得到属性值被修改的通知而做出相应的动作。 同样,当我们需要让外部访问类中私有部分的一个变量但不允许改动的时候,我们可以为类添加一个属性,并只指定read限定符来指向这个变量,不用write限定符,从而限制为只读。 其他: 我们在工作中,往往接到的需求是一个复杂的庞大的问题,而我们首先要做的就是分解这个问题,把一个大的问题分解成若干个小的问题,并把他抽象的提取出来,这就是我们类功能的雏形。 举个简单的例子,比如工程师建房子,房子有一层的小平房,有一百层大厦,但是我们可以换个角度去看这个问题:不管你建多少层楼,房子都是由X个房间组成的,区别只在于房间数量的不同而已。这就是他们的共性。 这里,再说一句,接到一个新的项目以后,第一件事不是立即兴奋的开始写代码,而是首先要做规划。 继续拿做房子来说(-_-|)任何一个工程师,在接到新的工程的时候,应该不会有任何一个人会先考虑怎么砌砖吧。或许你会因为项目小而放弃准备期,但是我想说一下,项目不分大小,准备工作是必不可少的。 曾经我听过一个小笑话:说有一个人,做一个鸟笼子,由于他只看过一次鸟笼子,而在他开始做之前又没有去实际的看过笼子的成品,而导致他的鸟笼做完后,竟然发现他犯了一个最离谱的错误——笼子上没有门。 这个小笑话其实也说明了预先的准备工作的重要性了。