《面向对象软件开发教程8.doc》由会员分享,可在线阅读,更多相关《面向对象软件开发教程8.doc(46页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、295 构建系统:面向对象编程 软件是在代码之外发生的一切事情。 第8章构建系统:面向对象编程你将从本章学到什么?l 什么是编程l 为什么说编程比写代码内容更宽泛l 如何用Java实现对象设计l 如何用结构化查询语言(SQL)实现持久性设计l 编程技巧、技术与习惯用语为什么需要阅读本章?所有为建模付出的努力的目标就在于,确保解决方案以一种有效而充分的方式满足用户需求。但是为了确保成功,程序员必须理解如何把设计模型转变成可以工作的源代码。在本章中,你将会了解到为系统开发面向对象代码以及持久代码的基础知识。面向对象编程的目标在于构建实际可用的系统,开发完成系统设计的代码。如图8-1所示,设计制品用
2、虚线框来描述,它们驱动着源代码的开发。与图3-1、图6-1、图7-1一样,箭头表示“驱动”关系,设计模型中的信息驱动源代码的开发,反之亦然。图8-1中最重要的含义是,设计和编程高度相关并且迭代。在编程中付出的努力很快就会揭示出设计过程中的弱点,这些地方需要表述出来。可能设计者没有意识到编程环境中的特定功能,因此也就没有利用它们。图8-1中没有表明的一点是,要把注意力放在两种类型的源代码上:用Java和C+等开发的面向对象源代码,持久机制源代码如数据定义语言(DDL)、数据操作语言(DML)、存储过程和触发器等。8.2节描述了怎样使用Java语言实现通用面向对象的概念,8.3节描述了持久性编码。
3、类模型、状态图、用户界面原型、业务规则与协作图驱动面向对象源代码的开发,而持久模型则驱动持久代码的开发。定 义业务规则(business rule):软件必须满足的策略。业务规则是功能需求所“了解”的内容,即由功能需求履行的控制和准则。一条操作原则或机构策略。图8-1 设计制品及其相互关系概述类图(class diagram):类图显示系统的类和类间的关联。类模型(class model):类图及其相关文档。协作图(collaboration diagram):一种UML图,用以显示类的实例、它们之间的相互关系以及它们之间的消息流。协作图的着眼点一般是发送和接收消息的对象的结构化组织。组件图(
4、component diagram):一种UML图,用以描述组成应用程序、系统或企业的软件组件。并且也描述组件、它们之间的相互关系、交互以及公共接口。数据定义语言(data definition language,DDL):由持久机制支持的命令,使得在它之内的结构(如关系表或类)创建、删除或修改变为可能。数据操作语言(data manipulation language,DML):由持久机制支持的命令,使得在它之内的数据访问变为可能,包括数据的创建、检索、更新和删除。部署图(deployment diagram):一种UML图,用以显示系统硬件、软件以及中间件的配置。开发/维护折衷(devel
5、opment/maintenance trade-off):加速开发过程的开发技术经常对系统维护工作量有负面影响,而导致更多维护工作的技术则负面影响了开发工作量,至少在短期内是这样的。持久机制(persistence mechanism):永久存储设施用来使对象持久。例子包括关系数据库、对象数据库、平面文件以及对象/关系数据库。持久模型(persistence model):一种描述软件系统持久数据方面的模型。原型(prototype):项目的模拟,例如用户界面或系统体系结构,使用它的目的是在投入重要资源之前把你的方法告知他人。状态图(state chart diagram):一种UML图,用
6、以描述对象所处的状态以及状态间的转移。以前也被称做状态图表(state diagram)或状态转移图(state-transition diagram)。存储过程(stored procedure):运行在持久机制中的一种操作。触发器(trigger):作为数据操作语言在持久机制内活动的结果自动触发的一种操作。用户界面(user interface,UI):软件的用户界面是与用户直接交互的部分,包括屏幕显示、报表、文档以及软件支持(通过电话、电子邮件等)。用户界面原型(user interface prototype):系统用户界面的一个原型。用户界面原型可以和手绘图一样简单,也可以像一组编程
7、屏幕显示、页面或报表一样复杂。提示:采取并遵循编程标准及准则 你的团队(理想情况下会是你的机构),必须就开发过程中遵循的标准及准则达成一致。编程标准和准则对于确保开发人员的工作达到机构需要的质量标准来说是很关键的。开发人员在机构中工作时应该遵守标准和准则,因此你就要检查他们是否按照要求做事。这些标准和准则应该在编程开始之时就定义/选择好,这样你的团队在开始之初就会有一个坚实的基础。8.1 什么是编程编程并不仅仅是编写代码。正如你在图8-2的程序过程模式(Ambler,1998b)解决方案中所见,编写代码仅是许多活动当中的一项。编程是一种迭代活动,它通过设计模型和项目基础设施(你选择的开发工具、
8、标准、准则和过程)来驱动。编程付出的努力的最终目标在于生产出一个打包应用程序,它可以经得起最终测试(第9章讲述)。图8-2 编程过程模式的解决方案除了编写代码外,编程还有大量的其他内容。图8-2揭示了编程过程真正的复杂性。它显示出程序员需要和建模人员共同工作以理解模型,并且要把相关反馈提供给建模人员。这将会产生重大的软件过程问题:如果建模人员和程序员不能一起有效地工作,那么为什么还要把建模放在第一位呢?程序员需要投入大量时间准备代码以备检测(检测操作在第9章中被描述成“局部测试”活动),还要积极参与整合构建过程。正如你预料的那样,程序员需要编写代码并归档代码(我以后会讨论到),在发现代码需要改
9、进时还要优化部分代码。好的开发人员能够理解整个开发过程。编程不仅仅是编写代码,在本书中你也会看到,软件开发也不仅仅是编程。直到理解了这张大图并理解如何适应它时,你就会把自己作为程序员的全部潜力发挥出来。我并不是说你需要在开发过程的任一阶段都成为一名专家;但是,你的确需要理解这些基础知识。好的开发人员开发中要有维护的思想,而不是仅局限于开发。开发人员需要学习的第二堂重要的课程是,开发不仅仅局限于开发。还要考虑像维护和支持这样的与生产相关的问题。实际上,应用程序确实把生命周期的大部分都花费在生产(后期开发)当中。一般计算机程序要花费生命周期的8090进行维护和支持,仅有一小部分用于开发。这意味着在
10、编程的时候头脑中时刻要有维护的思想,要能够完全预料到某天源代码需要更新,以反映新的需求或修改其中某个错误。在第10章中,你会看到全面的软件过程,如面向对象软件过程(Ambler,1998b;Ambler,1999)以及增强统一过程(Ambler和Constantine,2000a-c)等,它们都包含了后期开发活动这个阶段。开发/维护折衷最终归结为“一盎司的预防与一磅的治疗相当”。这里一个相关概念是被我称为开发/维护折衷的东西,也就是加速开发过程的决定经常会在维护过程中使你受罪,而完善系统可维护性则经常会增加最初开发系统的时间,至少在短期内确实如此。你看到了,越容易维护的代码就越有可能被复用。如
11、果代码很难维护,它也很难被复用。本章中我会提到有关代码可维护性的一些因素,文档、分段、智能命名策略以及好的设计,所有这些东西都需要在开发过程中花费时间和金钱。尽管他们有可能会为自己在维护过程中付出更多的时间,但短期的痛楚经常会使你把维护推迟到以后某个时间,而通常这个时间不会到来。真正的权衡应该是这样的:你是否在开发过程中投入了一点儿时间和努力,以更大地减少未来维护过程所付出的努力。我想答案应该是肯定的。定 义构建(building):软件产品自基础源码创建的过程。在Java和C+等编程语言中是编译和链接的行为,在像Smalltalk这样的语言中是打包代码的行为。整合计划或集成计划(integr
12、ation plan):描述整合系统元素的计划表、资源以及方法的一项计划。项目基础设施(project infrastructure):工具、标准、准则、过程和其他项目小组使用过的制品。8.2 从设计到Java代码在本节中我会给你展示如何把面向对象设计转变成Java源代码。尽管语法有所不同,但基本概念对于像C+和Smalltalk这样的面向对象语言来说却是相同的。像Visual Basic这样的面向对象语言可能不会支持本节描述的所有概念。事实上,如果你正在使用Visual Basic,我建议你参考Developing Applications with Visual Basic and UML
13、(Reed,1999),你可以把它当做本节的替代品。你要理解本节的目标在于为你介绍把模型转换成源码的基本技术,这很重要。本节的目标并不是教你一些Java编程的细节知识。图8-3描述了大学系统的一张简单的类图,第7章给出了好几个类图的修改版本。我提供修改版本的目的是为保持它的简单:这个例子描述了转换为代码时你需要理解的面向对象的一些主要概念。它不需要是大学系统设计的准确反映。正如你在图中所见,我并没有过多偏离最初的设计;相反,我把它简化了,所以我能把精力只集中于我所需要的内容上。为了把面向对象设计转换成Java代码,你需要知道如何实现:l 类。l 类的实例和静态属性。l 类的实例和静态方法。l
14、构造器。l 访问器方法。l 继承。l 接口。l 关联、聚合以及组合。l 依赖。l 协作。l 业务规则。8.2.1 用Java实现一个类你应该在类声明的正上方包含正确描述一个类的文档注释。图8-4描述了用Java语言声明了一个类“Person”时开头的源码。类本身在图8-4的底部声明,表8-1描述了每个词的含义。顶部是类的标题文档,使用了Java文档注释风格,它描述了类,指出了类的作者和版本号。“author”和“version”是Javadoc标签,它们是由Javadoc工具解析的预定义字符串,用以产生代码的外部文档(Vermeulen等,2000)。这部分文档不是很多,因为类中还没有太多内容
15、。一般我们要写下类的简短描述、任何可用的类的不变体、类的作者和类的版本。图8-3 大学系统的一张简单的类图图8-4 声明“Person”类表8-1 “Person”类定义中每个词的含义单 词含 义abstract 表示类没有实例。如果没有指明这个关键词,类就被假定为具体类public 表示系统中的其他类可以访问此类class 表示类正在被声明,其他选项可以是接口Person 被声明类的名称implements 可选关键词,指出类要实现的接口列表(或者,可选地,若类为抽象类时它的子类要实现的接口列表)IPersistent 类实现的一个接口。可能会是通过逗号分隔的接口列表。注意图8-3中如何表明
16、“Person”类实现“IPersistent”接口在机构内部要设定文档注释、C语言风格注释、单行注释的使用标准。在机构中,应该为Java语言或其他任何语言设置注释风格的准则。Java有三种注释风格:以“/*”开始、以*/结尾的文档注释,以“/*”开始、以“*/”结尾的C风格的注释,以“/”开始直到代码行结束的单行注释。我偏向于在接口、类、方法以及属性声明之前使用文档注释来记录它们,因为它们可以被Javadoc处理,生成外部源码文档。我用C风格的注释把不再用到的几行代码注释掉,这样是要谨防用户改变想法,或者是因为在调试过程中我要临时把它注释起来。最后,我在方法内部使用单行注释,用以描述业务逻辑
17、、部分代码以及临时变量的声明。要使用Java语言实现一个类,必须首先声明,然后声明它的属性,然后再定义它的方法。提示:了解Java风格的要素 除了了解语法和Java语言的细节之外,你还要了解如何使用Java书写高质量的代码,也就是书写易于理解、维护及改进的代码。换句话说,你要了解Java风格的要素。为了实现这个目标,我推荐你阅读下面这些书:The Elements of Java Style(Vermeulen等,2000)、Essential Java Style(Langr,2000)、Java 2 Performance and Idiom Guide(Larman和Guthrie,20
18、00)、Java In Practice(Warren和Bishop,1999)和More Java Gems(Deugo,2000)。8.2.2 用Java声明实例属性“Person”类实例属性的声明在图8-5中描述。“name”属性是一个简单的字符串(Java的一种基本类型),而“homeAddress”属性是一个对象,在本例中,是图8-3中描述的“Address”类的一个实例。每个属性都被赋值私有可见性,这是我们在第7章中已经详细讨论过的问题。属性类型紧跟可见性标识符之后被声明,在本例中,“name”属性的类型是“string”,“homeAddress”属性的类型是“Address”。
19、最后要指定属性的名称,语句要使用分号结尾(Java语句以分号结尾)。图8-5 声明“Person”类的属性定 义抽象类(abstract class):对象不能从中实例化的类。注释(comment):源码中的文档。C风格的注释(C-style comment):Java中的一种多行注释风格,以“/*”字符开始、以“*/”字符结尾。文档注释(documentation comment):Java中的一种多行注释风格,以“/*”字符开始、以“*/”字符结尾。也被称做Javadoc注释。不变体(invariant):实例或类的一组断言,在任何“稳定的”时间都必须为真,“稳定的”时间是对象或类的方法调
20、用之前或紧随调用之后的一段时间。Javadoc标签(javadoc tag):一个预定义文本字符串,以符号“”开始,它可以为了格式化的目的嵌入Javadoc文档。例如“returns”和“throws”标签。Javadoc:一种Java工具,用以解析Java源文件,把注释归档,并作为产生外部代码文档的基础。单行注释(single-line comment):Java中的一种以“/”开始的注释风格,任何跟在双斜杠后面直到行尾的东西都属于注释。静态属性(static attribute):类的所有实例共享的属性。类的每个实例都共享静态属性的惟一值。静态方法(static method):类级操作的
21、方法,可以由类的所有实例来执行。可见性(visibility):外部对象访问项目(如对象的属性或方法甚至类本身)的级别。为了给你演示如何用不同的可见性描述属性,我在图8-6中给出了“Address”类属性的声明。你可以声明非私有属性,但那通常不会是个好主意。 图8-6 声明类“Address”的属性有效地归档你的属性。在图8-5与图8-6中,我使用了文档注释描述每个属性,使Javadoc可以识别出我想包含在外部文档中的内容。我在文档里也用了Javadoc标签;“example” 方法这个术语起源于Smalltalk语言。方法通常指C+中的成员函数或Java中的操作。统一建模语言更进一步把方法定
22、义为逻辑和操作的代码。在UML中,操作是其特征标记(操作的名称,传给操作的参数定义以及操作的返回值)与方法(代码)的组合。如果你是一个搞学术的人或是CASE工具设计人员,你会觉得弄清楚它们之间的区别很有趣,但如果你是一个日常的编程人员,对此你不会有太多兴趣。因为我有Smalltalk的背景,贯穿本书我采用术语方法、操作和成员函数表示UML中操作的意思。是我自己设计的,“see”是一个标准的Javadoc标签。如在第7章指出的那样,我相信归档属性、不变体(如果有的话)以及提供相关例子的目的在于增加源代码的价值。The Elements of Java Style(Vermeulen等,2000)
23、是本系列丛书的第五本书,书中提供了关于如何归档属性的有效讨论。8.2.3 用Java实现实例方法抽象类可以声明抽象方法,方法必须在具体子类中声明。实例方法正如你所预料的那样实现:头文件中应该描述这个方法,声明方法特征标记,声明实现代码的Java语句。图8-7描述了类“Person”的方法“hasParkingPrivileges()”的实现。声明方法特征标记与声明以前你所见到的类和属性相似。首先,要声明方法的可见性;在本例中,方法是公共的(在第7章中你了解到,Java中的方法也可以是受保护的、私有的或默认的可见性)。再者,可选关键词“abstract”会出现,在Java中意味着这个类只定义了方
24、法特征标记,实现必须由具体子类完成。只有抽象类(你在图8-4中见到的“Person”类就是抽象的)才可以声明抽象方法。然后要显示方法的返回类型,在本例中是“boolean”。最后是方法名、一个开括号、参数声明列表(如果有的话)和一个闭括号。本例中,方法名称是“ParkingPrivileges”,方法没有参数。图8-7 “Person”类中“hasParkingPrivileges()”方法定 义结束行注释(endline comment):使用单行注释记录一行源代码,注释紧跟在同行代码后。也称做内嵌注释(inline comment)。实例属性(instance attribute):应用于
25、类的单个实例(对象)的属性。每个对象都有它自己的实例属性值。不变体(invariant):实例或类的一组断言,在任何“稳定的”时间都必须为真,“稳定的”时间是对象或类的方法调用之前或紧随调用之后的一段时间。基本类型(primitive type):内嵌进编程语言的一种属性。例如,Java包含像int、string和Boolean这样的基本类型。子类可以覆盖其父类中一部分或全部方法。图8-8给出类“Professor”的方法“hasParkingPrivileges()”的实现,这个例子描述了子类中方法怎样覆盖(重定义)其父类中的方法。因为“Professor”是一个具体类,它必须有(或继承)父
26、类中定义的任何抽象方法的实现。在图8-3的类图里你会看到,“Student”类也实现了自己版本的这个方法。在本例中,它返回false,因为在缺乏足够停车位的情况下,学生是不允许在大学场地里停车的。值得注意的有意思的一点是,这个方法的文档与图8-7中那个相似,最大的区别在于这个文档现在更加明确而已。图8-8 “Professor”类中“hasParkingPrivileges()”方法8.2.4 用Java实现静态方法和静态属性静态方法可以由单个类的所有实例执行。与此相似,静态属性也可以应用于单个类的所有实例。在图8-3的类图中,静态方法和静态属性下面都有下划线(这是UML标准),而实例方法和实
27、例属性则没有。在图8-9中,你看到“ZipCodeValidator”类的两个验证方法的实现,它们两个都是静态方法(注意在它的声明中用到了Java关键字“static”)。你看到参数要首先声明类型,接着是名称,这与UML首先声明名称然后是类型的方法相反。比较图8-7中的源码以及图8-3中的类图你就会明白我的意思。实际上,在Java里声明方法的顺序与UML中有很大的不同,这些事情你很快就会习惯的。与属性一样,参数类型可能是Java基本类型或者自定义的对象。 图8-9 实现类“ZipCodeValidator”中的方法“validateZipCode()”图8-9中的源码因为下面几个原因显得很有意
28、思:1源代码给出了重载方法的一个例子。方法“validateZipCode(zipCode, State)”和方法“validateZipCode(zipCode)”被重载了。重载方法是一个或多个方法(在同一个类中、在它的父类中或在它的子类中)具有相同的名字,不相同的参数。这在Java中是允许的,这使你可以定义相互间相似的方法族,这些方法有着不同的实现,如你在图8-9中所见。定 义重载(override):当你在子类中重新定义了一个属性或方法时我们就说你把它重载了。2重载在这儿可能不合适。在图8-9中我特地以我自己的方式命名了方法,这样我就有了一个重载的简单例子。事实上第二个方法可以命名为“v
29、alidateZipCodeFormat (zipCode)”,这样就可以使它的含义更明显一些。3每个方法都有好几条返回语句。注意每条语句都回答了方法声明中定义的类型(这两个例子中都是Boolean)。4每个方法都提供了一个Java异常处理的例子,异常处理使用“try/catch”结构。方法调用和语句都在try作用域内,也就是开关花括号内。如果它们当中任一句抛出了异常,通过Java的“throw”语句(图8-13是抛出异常的例子),那么处理就会在那点结束,catch子语就被调用。在这个例子中,有可能字符串到整数的转换会失败,当传给它的字符串中含有非数字字符,Integer.parseInt()
30、方法会抛出一个异常,这些东西你可以在JDK文档中查到。5方法有不同的可见性。在我的设计中,我想让其他对象调用“validateZipCode(zipCode, state)”方法,在本例中是对象address,这样它们就能验证邮政编码是否有效。因此,把这个方法定义成公共的。然而,验证邮政编码格式的行为尽管标准化成它自己的一个方法(Ambler,1998a),但在设计中并不需要提供给外部对象。因此,方法“validateZipCode (zipCode)”被声明成私有的。6方法头文件要足够好。就像在第7章中描述的那样,在方法头文件中,应该归档方法做了什么,它是怎样做的,方法中任何已知的bug,它
31、抛出的异常,任何可见性判定,方法的返回值,方法如何改变一个对象,调用方法的例子和任何应用的前提条件与后置条件。7内部文档要好。我也相信文档仅在其有意义的时候才需要增加,图8-9包含了充足的内部文档,可以让你弄清楚代码是如何工作的。注意文档怎样用简单的业务术语来书写,如“Ensure that a hyphen exists in the sixth position”、“Invoke charAt()with 5 as a parameter and compare it to a hyphen”,它们使用的都是一些简单的英文用语。在方法内部你应该对任何控制结构进行说明,这些结构包括代码完成了
32、什么事情、为什么代码做这些事情、局部变量、困难或复杂的代码以及可用的处理次序。8方法“validateZipCode(zipCode)”可能会有bug。尽管我没有创建测试用例来检验它,我怀疑如果我传入“-1701”,它是一个非法的邮政编码,但它是合法的数字且有五个字符,它可能会通过我现有的代码。面向对象测试将在第9章讨论。图8-10描述了类“ZipCodeValidator”静态属性“stateZips”的声明。它被声明为静态的;这样,这个类定义的方法都可以访问它。它被赋以“null”作为它的初始值。图8-10 类“ZipCodeValidator”的属性“zipToState”8.2.5 实
33、现构造器图8-11描述了类“Seminar”的构造器,这是一个静态方法,因为它要用到整个类上,它完成创建类实例的职责。Java规范是类构造器必须要与类本身同名,构造器名第一个字母要大写。这个方法采用课程对象作为其参数,课程对象也是一个讨论班,它赋给了“course”属性。我没有调用“course”的getter方法来给出你如何完成这件事情的例子,因为在构造器内部调用getter是比较危险的。问题在于,如果getter方法检查了这个类的另一个属性,而这个属性还没有被构造器设置,错误就会产生。构造器调用了课程对象的一个方法来维持两个对象间的关系(关系的实现在8.2.9节详述)。“Instructo
34、rs”属性被设置为新的向量实例,它被用来维持与“Professor”对象的关系。图8-11 类“Seminar”的构造器方法定 义前提条件(precondition):操作或用况正确运行的约束的表述。后置条件(postcondition):当操作或用况成功调用后,其状态属性的一种表述。异常(exception):指明一个非预期情况在某些软件中出现。在Java里,异常由方法抛出,以指明潜在的问题。重载(overload):当你定义了两个同名方法,但参数不同,我们就说你重载了它。这个构造器有一个与实例属性同名的参数。因为实例属性可以由类构造器方法访问,这个参数有称为名称隐藏的问题。为了解决这个问题
35、,方法必须指明属性全名,在本例中是“this.course”,以使它与参数区分开。空白区域可以改善代码的可读性。注意图8-11中空白区域的使用。空白区域增加了代码可读性。等号周围的空格使得分辨以下事实更容易:属性名在等号左边结束,新值在等号右边开始。更重要的是,在属性instructors赋值语句之前的空行有助于把它和course对象相关的语句分开。定 义构造器(constructor):一种方法,一般是静态方法,目的在于实例化或选择性地初始化对象。名称隐藏(name hiding):指的是在更高层次上使用相同的或至少相近的名称用于属性/变量/参数。名称隐藏最常见的一种误用是局部变量与实例属性
36、同名。空白区域(whitespace):空白,例如空白行或空格。8.2.6 用访问器封装属性访问器大大地增加了代码的健壮性。访问器方法有两种:setter方法和getter方法。setter方法修改属性的值,而getter方法获取属性的值。访问器给代码增加的开销很小,真实情况是性能损失对于其他因素(例如值得怀疑的数据库设计)来说是微不足道的。访问器有助于隐藏类的实现细节,这样也增加了代码的健壮性。每个属性至少有两个控制点,一个是setter方法,一个是getter方法,你就可以通过减少需要做出变动的点来增加类的可维护性。图8-12描述了“Person”类中属性“homeAddress”的get
37、ter方法和setter方法的实现。getter方法和setter方法很有用,因为它们可以使你封装重要的可用于数据属性的业务规则、转换逻辑和/或验证逻辑。例如,图8-13给出了setter方法的一个候选实现。看看它首先如何通过调用方法“validate()”来验证传给它的地址对象是否合法,如果它是合法的,然后就设置属性“homeAddress”。如果地址不合法,会抛出一个异常(如你在图8-9中所见,异常通过Java的try/catch语句由方法调用者处理)。 图8-12 “Person”类中属性“homeAddress”的getter方法和setter方法图8-13 方法“setHomeAdd
38、ress()”的一个候选实现在机构里可以推行的最重要的标准之一是应用访问器。一些开发人员不想使用访问器方法,因为他们不想多敲几次必要的按键(例如,对于getter方法,除了属性名之外还需要敲入“get”和“()”)。关键在于使用访问器可以增加可维护性与可扩展性,这就验证了它们的用途。提示:访问器是惟一能访问属性的地方 正确使用访问器方法的本质概念在于,允许直接访问属性的惟一方法是访问器自身。是的,在定义属性的类中使用方法直接访问私有属性是可能的,但你不会这么做,因为这将会增加类间的耦合。8.2.6.1 命名访问器除非属性名是一个布尔值(true或false),这时getter方法名为“is”加
39、属性名,否则getter方法应该贯以名称“get”加上属性名。不管属性类型是什么,setter方法都要按给定的名称“set”加属性名来命名(Gosling、Joy和Steele,1996)。如你在表8-2中所见,属性名总是大小写混合的,每个词的首字母都应该是大写。命名规范与Java开发工具包(JDK)中是一致的,这也是JavaBeans开发所必需的。表8-2 访问器命名示例属 性名 称getter方法名setter方法名namehomeAddresspersistentzipCodeinstructorsstringAddress objectbooleanintVector of Profe
40、ssor objectsGetNameGetHomeAddressIsPersistentGetZipCodeGetInstructorssetNamesetHomeAddresssetPersistentsetZipCodesetInstructors8.2.6.2 访问器可见性对应的getter方法和setter方法经常有着不同的可见性。你应该总是尽量使访问器是受保护的,这样的话仅有子类可以访问属性。如果子类不需要访问属性,那么就要试着把访问器改为私有的。仅当外部类或对象需要访问属性时,才需要把合适的getter方法或setter方法变为公有的。如你在图8-3中所见,相应的setter方法
41、和getter方法的可见性不同,这很平常:在“Seminar”类中,你看到方法“getName()”有公有可见性,而方法“setName()”则具有私有可见性。图8-15显示出setter方法仅在getter中调用以格式化讨论班名称,讨论班名称是课程号、讨论班号和课程名称的组合。图8-14 在方法中调用访问器定 义访问器(accessor):用以修改或检索单个属性值的操作。也被称做getter操作和setter操作。getter方法:获取对象或类的数据属性值或计算这些数据值的一个方法。setter方法:设置对象或类的数据属性值的方法。也被称做mutator。惰性初始化给getter方法带来了复
42、杂性,但也潜在地增加了系统性能。图8-15中“getName()”方法的实现是一个被称为惰性初始化(lazy initialization)的例子,这种方法仅在属性值被初次访问时才进行初始化(设置)。这种方法的优点在于仅当需要获取值时才会引起开销。表面上,这不会给确定讨论班名称节省太多开销,但当课程对象驻留在另一台服务器上时就的确可以。这时需要把它讲读到永久存储中,这样就需要在网络上传输讨论班名称。惰性初始化最大的缺点是代码将会变得很复杂,因为你需要检查属性是否已经定义,如果没有,就要先得到它的值。惰性初始化一般在计算代价昂贵或获取代价昂贵时使用(可能会很大,要花费很多时间在网络上传输),或者
43、在并不是每次都要把对象写进内存中时使用。 图8-15 “Seminar”类中属性“name”的getter方法和setter方法定 义惰性初始化(lazy initialization):当getter方法首次被调用时属性初始值才会在其相应的getter方法中设置的一种方法。8.2.6.3 为什么使用访问器访问器在以下面几种方式增进了类的可维护性:1更新属性。使你有单独的地方来更新每个属性,使它更容易修改和测试。换句话说,你的属性将会被封装起来。2获取属性值。使你可以完全控制属性值如何以及由谁访问。3获取常量值。通过在getter方法中封装常量值,当这些值发生变化时,仅需在getter方法中更
44、新值,而并不需要在常量被使用的每一个位置都进行更新操作。常量经常被实现成Java接口的静态属性;这样,我就避免了在接口定义中包含静态属性来支持静态getter方法。4初始化属性。使用惰性初始化保证了属性总在需要时才做初始化。5减少子类及其父类间耦合。当子类仅通过相应的访问器方法访问继承属性时,这会使得在父类中改变属性实现不会影响到子类,这样就有效地减少了它们之间的耦合。访问器减少了“脆弱的基类问题”(fragile base class problem),所谓“脆弱的基类问题”就是说父类中的变化会波及子类。6把变化封装在属性中。如果与一个或多个属性相关的业务逻辑发生了变化,你都可以修改访问器以
45、提供和变化之前相同的功能,这会使你访问新的业务逻辑变得很容易。7名称隐藏变得不再是个问题。尽管你应该避免名称隐藏(局部变量与属性有相同的命名),访问器总是用来访问属性,这意味着你可以给局部变量任何你想取的名字。你并不需要担心会隐藏属性名称,因为必竟你从来也不会直接访问它们。定 义名称隐藏(name hiding):指的是在更高层次上使用相同的或至少相近的名称用于属性/变量/参数。名称隐藏最常见的一种误用是局部变量与实例属性同名。8.2.6.4 为什么你不应该使用访问器并不需要让所有的访问器都是公有的。惟一可能不需要用到访问器的时候是当执行时间是最为重要的考虑因素时,但这种情况很少见,实际上,应
46、用程序间增加的耦合也证明了这种做法是合理的。Lea(1997)举了个例子说明为什么要尽量地少使用访问器,他的依据是组合属性的值经常要保持一致,这时提供单独对属性的访问是不明智的做法。他是正确的,你不要对每个属性值都使用访问器。我想Lea没有注意到这一点,并不需要让所有的访问器都是公有的。当遇到属性值相互依赖这种情况时,你应该引入完成“正确事情”的方法,使访问器方法按需设置为受保护的或私有的。8.2.7 用Java实现继承图8-16描述了使用Java语言怎样使一个类继承另一个类:当类声明时使用关键词“extends”。Java仅支持单一继承,类的功能继承于零个或一个类,这与C+不同,C+支持多重继承,功能继承于零个或多个类。因为“Student”继承于“Person”,我们就说“Student”是“Person”的子类,“Person”是“Student”类的父类。图8-16 指出继承“Person”类的“Student”类8.2.8 用Java实现接口用Java实现接口有两个方面:接口本身的实际定义,及实现接口的类代码的定义。8.2.8.1 定义一个接口在图8-3中,你看到一个名为“IPersistent”的接口。在UML类图中接口定义用类框加上构造型指出。在图8-3中,我与通用的UML符号表示方法不同,我在接口名下面指明interface