代码大全:类

第二部分 建立高质量的代码程序员

 

第6章 能够工做的类编程

在计算时代的早期,程序员基于语句来思考编程问题。到了二十世纪七八十年代,程序员开始基于子程序去思考编程。进入二十一世纪,程序员以类为基础思考编程问题。【思考编程的角度有不少,可是写程序最后都是要落实到语句的层面。语句层面不能精准的实现想要的功能,那么程序确定会出错。我没有深刻过一种如今的面对对象语言,也就是说我对如今的“高效”编程语言了解的确很少。所以我不能肯定是否可以不须要花费太多精力在语句实现上,仅仅在类的层面精心设计,在语句上使用轻松写意的手法,就可以很容易得实现功能。不过,若是从编程的进化方向来看,这是个先进的。】数组

类是由一组数据和子程序构成的集合,这些数据和子程序共同拥有一组内聚的、明肯定义的职责。成为高效程序员的一个关键就在于,当你开发程序任一部分的代码时,都能安全地忽视尽量多的其他部分。而类就是实现这一目标的首要工具。【这听起来至关不错,若是你的工程师由多职责明确的小玩意组成的,那么你一时间能够只集中注意力在一个小玩意上。】安全

 

6.1 类的基础:抽象数据类型ADTs数据结构

抽象数据类型是指一些数据以及对这些数据所进行的操做的集合。这些操做既向程序的其他部分描述了这些数据是怎么样的,也容许程序的其他部分改变这些数据。抽象数据类型中的数据一词的用法有些随意。一个ADT多是一个图形窗体以及全部能影响该窗体的操做;也能够是一个文件以及对这个文件进行的操做;或者是一张保险费率表以及相关的操做。【ADT就像一个在现实世界中的实体,你一看过去就知道它是什么,能对你作什么】编程语言

传统的编程教科书在讲到抽象数据类型时,总会用一些数学中的事情打岔。这些书每每会这么写:你能够把抽象数据类型想成一个定义有一组操做的数学模型。【哇哦,写这种书的人必定对数学模型非常了解。隐喻经常是很是私密性的,对你感受很好的隐喻对其他人来讲却激不起一样的感受。不过,使用一些你们都很熟悉的隐喻对象,对于广泛大众来讲,效果会比较好。】这种书会给人一种感受,好像你从不会真正用到抽象数据类型似的——除非用它来催眠。【我能理解这种感受,对于不少高深的数学名词个人感受老是深感敬畏,敬而远之。看来编程大师们也是同样的,这却是件新鲜事,我还觉得大家探索数学领域就如同在自家后花园散步同样呢。】函数

把抽象数据类型解释得那么空洞是彻底丢了重点。抽象数据类型可让你像在现实世界中同样操纵实体,而没必要在底层的实现上摆弄实体,这多使人兴奋啊。【比起在底层上摆弄变量,语句,来间接控制实体;能直接操纵实体听起来简直让人想留下热泪。我认可我喜欢在最底层控制机器,这感受棒极了。可是这不表明我认为在只是要对机器动做作一点微小的改变的状况下,就必须得翻山越岭,战胜各类bug,各类推理,各类流程,作完以后再开个香槟庆祝下。感受上,这得是多么古老的程序员才能作出的事情啊···仰面长叹。我但愿我有一天可以轻松地操纵机器,就像操纵我能轻松的操纵我本身的手臂同样。】你不用再向链表中插入一个节点了,而是能够在电子表格中添加一个数据单元格,或给火车模型加一节车箱。深刻挖掘能在问题领域工做(而非在底层实现领域工做)的能量吧!工具

 

须要用到ADT的例子性能

为了展开讨论,这里先举一个例子,看看ADT在什么状况下回很是有用。有了例子以后咱们将继续深刻细节讨论。测试

假设你正在写一个程序,它能用不一样的字体、字号和文字属性(如粗体、斜体等)来控制显示在屏幕上的文本。程序的一部分功能是控制文本的字体。若是你用一个ADT,你就能有捆绑在相关数据上的一组操做字体的子程序——有关的数据包括字体名称、字号和文字属性等。这些子程序和数据集合为一体,就是一个ADT。

若是不使用ADT,你就只能用一种拼凑的方法来操纵字体了。举例来讲,若是你要把字体大小改成12磅,既高度碰巧为16个像素,你就要写相似这样的代码 

currentFont.size = 16

若是你已经开发了一套子程序库,那么代码可能会稍微好看一点:

currentFont.size = PointsToPixels(12)

或者你还能够给该属性起一个更特定的名字,好比说:

currentFont.sizeOnPixels = PointsToPixels(12)

但你不能同时使用currentFont.sizeInPixels 和 currentFont.sizeInPoints,由于若是同时使用 currentFont.sizeInPixels 和 currentFont.sizeInPoints,这两项数据成员,currentFont 就无从判到底该用哪个。【同时使用两项数据成员,我听不出这有什么问题?】并且,若是你在程序的不少地方都须要修改字体的大小,那么这类的语句就会散步在整个程序之中。【的确,像这种细枝末节的东西应该被限制使用。最好实现一次原则,完成一个修改只须要在一个地方修改一处就能够。】

若是你须要把字体设置为粗体,你或许会写出下面的语句,这里用到了一个按位or运算符和一个16进制的常亮0x02:

currentFont.attribute = CurrentFont.attribute or 0x02

若是你够幸运的话,也可能代码会比这样还要干净些。但使用拼凑方法的话,你能获得的最好的结果也就是写成这样:

currentFont.attribute = CurrentFont.attribute or BOLD 

或者是这样:

currentFont.bold = True

就修改字体大小而言,这些作法都存在一个限制,即要求调用方代码直接控制数据成员,这无疑限制了currentFont的使用。【不须要你直接控制数据成员,只要告诉我你想要作些什么。这感受比较像人工智能,我一直但愿我能够直接和个人机器交流,问它能作什么,而不是不停的翻阅NOTE。】

若是你这么编写程序的话,程序中的不少地方就会充斥着相似的代码。【我以前写的程序也有不少这种代码,像 currentFont.bold = True 这样的代码,可能还被封装成了函数。简单的批了一层皮,用处可不大,o(* ̄︶ ̄*)o 改起来基本靠搜索引擎。。。】

 

使用ADT的益处 

问题不在于拼凑法是种很差的编程习惯。而是说你能够采用一种更好的编程方法来替代这种方法,从而得到下面这些好处。【接下来是说服时间,讲讲为何咱们要使用ADTs编程。其实使用ADT的理由能够很是简单,它更加“界面友好”,符合人们对美好生活的向往,让一切变得更加简单。不过,看看,更加理性的理由表是好点子。人们由于感受而行动,可是却不能单靠感受就将一件须要花费超过几个小时的时间才能完成的事情作好。】

能够隐藏实现细节  把关于字体数据类型的信息隐藏起来,意味着若是数据类型发生改变,你只须要在一处修改而不会影响到整个程序。例如,除非你把实现细节隐藏在一个ADT中,不然当你须要把字体类型从粗体的第一种表示变成第二种表示时,就不可避免的要更改程序中全部设置粗体字体的语句,而不能仅在一处进行修改。【在决定外放使用时,就要考虑可能的变动,以及变动意味着什么。】把信息隐藏起来能保护信息的其他部分不受影响。【对一个模块的改变最好仅仅在其内发生,模块的外部接口保持不变。】即便你想把在内存里存储的数据改成在外存里存储,或者你想把全部操做字体的子程序用另外一种语言重写,也都不会影响程序的其他部分。

改动不会影响到整个程序  若是想让字体更丰富,并且能支持更多的操做(例如变成小写大写字母、变成上标、添加删除线等)时,你只须要在程序的一处进行修改便可。这一改动不会影响到程序的其他部分。

让接口能提供更多的信息  像 currentFont.size = 16 这样的语句是不够明确的,由于此处16的单位既多是像素也多是磅。语句所处的上下文环境并不能告诉你究竟是哪种单位。把全部类似的操做都集中到一个ADT里,就可让你给予磅数和像素来定义整个接口,或者把两者明确的区分开,从而有助于避免混淆。

更容易提升性能  若是你想提升操做字体时的性能,就能够重写编写出一些更好的子程序,而不用来回修改整个程序。

让程序的正确性显而易见  验证像 currentFont.attribute = currentFont.attribute or 0x02 这样的语句是否正确是很枯燥的,你能够替换成像 currentFont.SetboldOn()这样的语句,验证它是否正确就会更容易一些。对于前者,你可能会写错结构体或数据项的名字,或者用错运算符,也可能会写错数值。但对于后者,在调用  currentFont.SetboldOn() 时,惟一可能出错的地方就是写错方法名字,所以识别它是否正确就更容易一些。

程序更具自我说明性  你能够改进像 currentFont.attribute or 0x02 这样的语句——把0x02换成BOLD或者其余0x02所表明的具体含义,但不管如何修改,其可读性都不如 currentFont.SetboldOn() 这条语句。【直接告诉你在作什么。】

Woodfield、Dunsmore 和 Shen 曾作过这样一项研究,他们让一些计算机科学专业的学生回答关于两个程序的问题:第一个程序按功能分解为8个子程序,而第二个程序分解为抽象数据类型中的8个子程序(1981)。结果,那些使用按抽象数据类型程序的学生的得分比使用按功能划分的程序的学生高出超过30%。【是否是只有我一我的怀疑这两个项目的难度不一致?我以为想得出这种结论,应该将一个程序按两种方法分解,而后测试。这里多是记诉错误。能够去查询下当年的实验说明。】

无须在程序内处处传递数据  在刚才那个例子里,你必须直接修改 currentFont 的值,或这把它传给要操做字节的子程序。若是你使用了抽象数据类型,那么就不用再在程序里面处处传递 currentFont 了,也无须把它变成全局数据。【抽象数据类型有一个优势,使用者不须要知道内部实现细节。为了支持这一点,抽象数据类型须要作好隐私保护工做。因此,本身的变量是不能传出去给别人修改的,由于其他部分若是须要直接修改你的特征值,那么就须要知道你的特征值的具体使用规则,那么你的这些具体的条条框框就随时暴露在其他模块的视线之下,成为它们须要额外注意的规则之一。老实说,就没人可以随时记得这些,大脑的内存也是颇有限的。】ADT中能够用一个结构体来保存 currentFont 的数据,而只有ADT里的子程序才能直接访问这些数据。ADT以外的子程序则没必要再关心这些数据。【乍看之下是限制了一部分操做,实际上确实大大方便了各模块的使用。所谓松散耦合。】

你能够像在现实世界中那样操做实体,而不用在底层实现上操做它  你能够定义一些针对字体的操做,这样,程序的绝大部分就能彻底以“真实世界中的字体”这个概念来操做,而再也不用数组访问、结构体定义、True 与 False 等这些底层的实现概念了。【天,我一度认为True好用得不得了。】

这样以来,为了定义一个抽象数据类型,你只须要定义一些用来控制字体的子程序——多半就像这样:

currentFont.SetSizeInPoints(sizeInPoints)

currentFont.SetSizeInPixels(sizeInPixels)

currentFont.SetBoldOn()

currentFont.SetBoldOff()

currentFont.SetItalicOn()

currentFont.SetItalicOff()

currentFont.SetTypeFace(faceName)

这些子程序里的代码可能很短——极可能就像你此前看到的那个用拼凑法控制字体时所写的代码。【只有一句。实际上全部代码自己都是拼凑而成的,拼一拼,而后实现个功能。关键在于对外的接口足够“用户友好”。而内部功能实现则讲究另外一套法则,原始规则,要求准确、快速、健壮、占用内存少。。。】这里的区别在于,你已经把对字体的操做都隔离到一组子程序中了。这样就为须要操做字体的其余部分程序提供了更好的抽象层,同时它也能够在针对字体的操做发生变化时提供一层保护。【隔离功能,可使得该功能内部能够自由的实现。】【这里要注意,少给我玩假的,一眼就能被看穿。】

 

更多的ADT示例

假设你开发了一套软件来控制一个核反应堆的冷却系统。你能够为这个冷却系统规定以下一些操做,从而将其视做一个抽象数据类型:

coolingSystem.GetTemperature()

coolingSystem.SetCirculationRate(rate)  【流通率 / 周期 我也不清楚】

coolingSystem.OpenValue(valueNumber)

coolingSystem.CloseValue(valueNumber)

实现上述各操做的代码由具体环境决定。程序的其他部分能够用这些函数来操纵冷却系统,无须为数据结构的实现、限制及变化等内部细节而操心。

下面再列举一些抽象数据类型以及它们可能提供的操做:

巡航控制

设置速度

获取当前设置

恢复以前的速度

解散 

 

搅拌机 

开启

关闭

设置速度

启动“即时粉碎器”

中止“即时粉碎器”

 

油罐 

填充油罐

排空油罐

获取油罐容积

获取油罐状态 

 

列表 

初始化列表

向列表插入条目

从列表删除条目

读取列表中的下一个条目

 

灯光

开启

关闭

 

堆栈

初始化堆栈

像堆栈中推入条目

从堆栈中弹出条目

读取栈顶条目 

 

帮助屏幕

添加帮助项

删除帮助项

设置当前帮助项

显示帮助屏幕

关闭帮助显示

显示帮助索引

返回前一屏幕 

 

菜单

开始新的菜单

删除菜单

添加菜单项

删除菜单项

激活菜单项

禁用菜单项

显示菜单

隐藏菜单

获取菜单选项

 

文件

打开文件

读取文件

写入文件

设置当前文件位置

关闭文件

 

指针 

获取新分配内存的指针

用现有的指针释放内存

更改已分配的内存大小

 

电梯

到上一层

到下一层

到指定层

报告当前楼层

回到底层

 

【判断目标有那些操做的诀窍就在于:你想要怎么使用它?使用测试驱动开发方法会有帮助。】

经过研究这些例子,你能够得出一些指导建议,下面就来讲明这些指导建议:

把常见的底层数据类型建立为ADT并是有那个这些ADT,而再也不使用底层数据类型  大多数关于ADT的论述中都会关注与把常见的底层数据类型表示为ADT。从前面的例子中能够看到,堆栈、列表、队列以及几乎全部常见的底层数据类型均可以用ADT来表示。

你可能会问:“这个堆栈、队列、或列表又是表明什么呢?”若是堆栈表明的是一组员工,就该把它看做是一些员工而不是堆栈;若是列表表明的是一个出场演员名单,就该把它看做是出场演员名单而不是列表;若是队列表明的是电子表格中的一组单元格,就该把它看做是一组单元格而不是一个通常的队列。【对了,事物自己的特殊性!我总不能由于以为程序像水母,那么就想办法将水母的全部特性都加在程序的身上,好比优雅可爱,并直接忽视程序自己的特性,好比,底层是语句/命令拼凑而成。】

相关文章
相关标签/搜索