集合的存取函数
存取函数的主要目的是将访问封装到字段,以减少代码的耦合。集合,如数组和矢量,要比单值复杂,实现起来自然不只是需要获取和设置成员函数。特别是因为要对集合进行增减,所以需使用存取成员函数。在集合字段的适当处加入如下存取成员函数:
成员函数类型 | 命名约定 | 示例 |
集合获取函数 | getCollection() | getOrderItems() |
集合设置函数 | setCollection() | setOrderItems() |
在集合中插入一个对象 | insertObject() | insertOrderItem() |
从集合中删除一个对象 | deleteObject() | deleteOrderItem() |
生成并且插入一个新对象到集合中 | newObject() | newOrderItem() |
这种方法的优点是集合被完全封装了,允许你以后用另外一个结构,可能是链表或是 B 树来取代它。
同时访问几个字段
存取成员函数的一个优点是,它使你能有效地执行业务规则。考虑如下一个有关形状 (Shape) 的类的层次结构。Shape的每一个子类通过 xPosition 和 yPosition 这两个字段表示位置,并且可以通过调用成员函数 move(Float xMovement, Float yMovement) 在屏幕上的二维坐标下移动。为达到我们的目的,图形在任何时刻都不可以只在一个坐标轴方向上移动,而应同时沿 x 和 y 轴移动(成员函数 move() 的两个参数中的任何一个参数都可以是 0.0)。 这就意味着 move()成员函数应该是公有的,但是 setXPosition() 和 setYPosition() 应该是私有的,被 move() 成员函数正确调用。
另一个实现方法是,引入一个可以同时更新两个字段的设置成员函数,如下文所示。成员函数 setXPosition() 和setYPosition() 应该仍然是私有的,这样它们不会被外部类和子类直接调用(要加入一些如下文所示的注释来说明它们不应被直接调用)。
/** 设定图形位置 */
protected void setPosition(Float x, Float y)
{
setXPosition(x);
setYPosition(y);
}
/** 设置 x 坐标。重要:调用 setPosition(),不是这个成员函数。*/
private void setXPosition(Float x)
{
xPosition = x;
}
/** 设置图形的 y 坐标
重要:调用 setPosition(),不是这个成员函数。
*/
private void setYPosition(Float y)
{
yPosition = y;
}
存取函数的可见性
尽可能地让字段成为被保护 (protected) 类型,这样只有子类可以访问它们。仅当一个外部类需要访问一个字段时,才将相应的获取函数或设置函数置为公有。注意:获取函数是公有而设置函数是私有的情况经常发生。
有时需要将设置函数设为私有以保证某个常量不变。例如,Order 类可能含有一个字段表示 OrderItem 实例的集合,含有另一个叫 orderTotal 的字段表示整个定单 (order) 的总和。orderTotal 是一个表示订购项目子类总和的有用字段。唯一可以更新 orderTotal 值的成员函数是那些处理订购项目集合的函数。假设那些成员函数都在 Order 中实现,那么即使getOrderTotal() 很可能是公有,也应设 setOrderTotal() 为私有。
一定要初始化静态字段
静态字段,也叫类字段,应被赋予有效值,因为不能假定类的实例将在一个静态字段被访问之前生成。
局部变量标准
局部变量是指在一个块(通常是一个成员函数)内定义的对象或者数据项。一个局部变量的作用范围是定义它的块。局部变量的一些重要的程序设计标准集中在:
· 命名约定
· 注释约定
· 声明
命名局部变量
一般说来,命名局部变量遵循与命名字段一样的约定,即使用完整的英文描述符,任何非开头的单词的第一个字母要大写。
但是为方便起见,对于如下几个特殊的局部变量类型,这个约定可以放宽:
· 流
· 循环计数器
· 异常
命名流
当有一个单输入和/或单输出流在一个成员函数中被打开、使用和关闭时,通常的约定是对这些流分别采用 in 和 out[GOS96] 来命名。对于既用于输入又用于输出的流,采用 inOut 来命名。
一个常用的取代这种约定的方法是分别采用 inputStream,outputStream 和 ioStream 这样的名字,而不是 in,out 和inOut,虽然这与 Sun 公司的建议相抵触。
命名循环计数器
因为局部变量常用作循环计数器,并且它为 C/C++ 所接受,所以在 Java 编程中,可以采用 i, j 或 k 作为循环计数器[GOS96]。 若采用这些名字作为循环计数器,要始终使用它们。
一个常用的取代方法是,使用如 loopCounter 或只是 counter 这样的名字,但是这种方法的问题是,在一个需要多个计数器的成员函数中,常常发现象 counter1 和 counter2 这样的名字。 概括起来说,i,j,k 作为计数器时,它们可以很快被输入,它们被广泛的接受。
命名异常对象
因为在 Java 代码中异常处理也非常普遍,所以字母 e 作为一般的异常符被广泛地接受 [GOS96]。
声明和注释局部变量
在 Java 中声明和注释局部变量有几种约定。这些约定是:
1. 一行代码只声明一个局部变量。这与一行代码应只有一个语句相一致,并使得对每个变量采用一个行内注释成为可能。
2. 用一个行内注释语句说明局部变量。行内注释是一种紧接在同一行的命令代码后,用符号 // 标注出来的单行注释风格(它也叫“行末注释”)。应注释出一个局部变量用于做什么、在哪里适用、为什么要用等等,使代码易读。
3. 仅将局部变量用于一件事。一旦将一个局部变量用于多个原因,就明显降低了它的一致性,使它难于理解。同时也增加了代码因为局部变量旧值的意外负面影响而产生问题的可能性,这些旧值来源于前面的代码。的确,局部变量的重新利用需要较少的内存,因而更高效,但是复用局部变量降低了代码的可维护性,使代码脆弱。这常常让由于不必分配更多内存而带来的小节省变得不值得。
关于声明的一般注释
在代码行间,如在一个 if 语句作用域内,声明的局部变量对于不熟悉你的代码的人来说是难于找到的。
一种取代在第一次使用局部变量之前声明它们的方法是在代码的前部声明它们。函数应该简短,参见(第 2.4.5 节“写出简短单独的命令行”),所以要去代码顶部判断局部变量用途的工作并不是很糟。
成员函数参数标准
有关成员函数参数的重要标准集中在参数应如何命名和说明。参数指成员函数的实参。
命名参数
参数命名遵循与局部变量命名完全一样的约定。对于局部变量,名字隐藏是一个问题。
示例:
customer
inventoryItem
photonTorpedo
in
e
一个可行的取代方法,例如 Smalltalk,是采用局部变量的命名约定,但在名字之前加入“a”或“an”。加上“a”或“an”有助于让参数与局部变量和字段区分开来,避免名字隐藏的问题。这种方法较好。
示例:
aCustomer
anInventoryItem
aPhotonTorpedo
anInputStream
anException
注释参数
成员函数的参数在采用 javadoc @param 标识的头文件中注释。应说明:
1. 参数用来做什么。需要注释出参数用来做什么,以便其他开发者了解使用参数的上下文。
2. 任何约束或前提条件。 如果一个参数的值域不能被成员函数接收,则应让调用者知道。可能一个成员函数只接收正数,或者字符数小于五的字符串。
3. 示例。如果应传递什么样的参数不明显,那么应该在注释中给出一个或多个例子。
采用参数类型接口。若合适的话,不要只说明参数类型属于哪一类,如 Object,而应说明属于哪个接口,例如 Runnable。这样的好处是,这种有赖于环境的方法更具体(Runnable 比 Object 更具体),或者在支持多态性(不坚持一个参数是一个类的层次结构中某个类的实例,而说明它支持一个特定的接口,这意味着它只用多态地适应你的需要即可)上是一个更好的方法。
类、接口、包和编译单元的标准
这一章集中讲述类、接口、包和编译单元的标准和指南。类是一个可以让对象创建的模板。类包含字段和成员函数的声明。接口是公共标识的定义,包括成员函数和字段。使用接口的类必须支持这些函数和字段。包是一个相关类的集合。最后,编译单元是声明类和接口的源码文件。因为 Java 允许编译单元放在数据库中,所以一个单独的编译单元可能不与一个源码文件物理上直接相关。
类的标准
类的一些重要标准基于:
· 命名约定
· 注释约定
· 声明约定
· 公共和保护接口
命名类
标准 Java 约定是使用完全的英文描述符,所有单词的第一个字母要大写,并且单词中大小写混合。
类名应是单数形式。
示例:
Customer
Employee
Order
OrderItem
FileStream
String
注释类
以下的信息应写在文档注释中紧靠类的定义的前面:
1. 类的目的。开发者需要了解一个类的一般目的,以判断这个类是否满足他们的需求。养成一个注释与类有关的任何好东西的习惯,例如:它是否是一个模式的一部分,或是使用它时有什么要引起注意的限制 [AMB98]。
2. 已知的问题。如果一个类有任何突出的问题,应说明出来,让其他的开发者了解这个类的缺点/难点。此外,还应注明为什么不解决问题的原因。注意:如果问题仅仅针对一个成员函数,那么它应直接与那个成员函数相联系。
3. 类的开发/维护历史。通常要包含一个历史记录表,列出日期、类的作者和修改概要。这样做的目的是让进行维护的程序员了解过去曾对一个类所做的修改,是谁做了什么样的修改。
4. 注释出采用的不变量。不变量是指一套有关实例或类在所有“稳定”时间片内为“真”的声明。“稳定时间片”是指在一个成员函数被对象/类调用之前和立刻调用之后的时间 [MEY88]。通过说明一个类的不变量,你让其他的开发者了解应该如何使用一个类。
5. 并行策略。任何采用 Runnable 接口的类应充分说明它的并行策略。对许多程序员来说,并行编程是一个新的而且复杂的题目,所以需要投入一些额外的时间来确保人们能够读懂你的东西。说明你的并行策略以及为什么选取这个策略而不是其它策略这很重要。常用的并行策略 [LEA97] 包括下面一些内容:同步对象;停滞 (balking) 对象;警戒 (guarded) 对象;版本 (versioned)对象;同步策略控制器;接收器。
类的声明
一种让你的类容易被理解的方法是用一致的方式来声明它们。Java 中常用的方法是按如下顺序声明一个类:
公共成员函数
公共字段
被保护成员函数
被保护字段
私有成员函数
私有字段
[LAF97] 指出,构造函数和 finalize() 应该首先列出,可能是因为这些将会是另一个开发者为了解如何使用一个类而首先查看的成员函数。此外,因为我们有一个要求将所有字段声明为私有的标准,所以声明顺序实际应变为:
构造函数
finalize()
公共成员函数
被保护成员函数
私有成员函数
私有字段
在每一个成员函数分组内,常将函数按照字母表的顺序列出来。许多开发者在每个组内有选择性地列出了静态成员函数,然后列出实例成员函数,再在这两个子分组内按照字母表的顺序列出成员函数。这两种方法都可用,你只需选用一种并且一直用它。
更多游戏技术方面的问题请访问游戏技术网: