要指出的一点是,将常量作为字段来使用不够灵活,一个好得多的办法是将常量作为获取函数来处理。上例中,采用一个叫做 getMinimumBalance() 的静态成员函数比一个叫做
MINIMUM_BALANCE 的静态字段要灵活得多,因为在成员函数中我们可以实现不同的业务规则,并针对不同的帐号生成不同的子类。
/** 得到帐号值。帐号号码的格式如下: BBBBAAAAAA,BBBB 是支行号码, AAAAAA 是支行帐号号码 */
public long getAccountNumber() {
return ( ( getBranchNumber() * 100000 ) + getBranchAccountNumber() ); } /**
设定帐号号码。帐号号码的格式如下: BBBBAAAAAA,BBBB 是支行号码, AAAAAA 是支行帐号号码 */
public void setAccountNumber(int newNumber) {
setBranchAccountNumber( newNumber % 1000000 ); setBranchNumber( newNumber / 1000000 ); }
常量获取函数的另一个优点是,它们有助于提高代码的一致性。考虑上述代码,它们不能正常工作。一个帐户号码是支行号和支行帐号号码的结合。测试我们的代码,我们发现设置成员函数
setAccountNumber() 不能正确更新支行帐号(它取了最左边的三位数,而不是四位)。那是因为我们采用了 1,000,000,而不是 100,000 来提取字段 branchAccountNumber。 若如下面所示,对这个值采用一个单独的来源,即常量获取函数
getAccountNumberDivisor(),我们的代码将更一致,并能正常工作。
/**
返回要求的除数,用来将整个帐户号分割 为支行号和支行帐户号。 整个帐号的格式是 BBBBAAAAAA。 */
public int getAccountNumberDivisor() {
return ( (long) 1000000);
} /**
得到帐号值。帐号号码的格式如下: BBBBAAAAAA,BBBB 是支行号码, AAAAAA 是支行帐号号码 */
public long getAccountNumber()
{
return ( ( getBranchNumber() * getAccountNumberDivisor() ) * getBranchAccountNumber() ); } /**
设定帐号号码。帐号号码的格式如下: BBBBAAAAAA,BBBB 是支行号码, AAAAAA 是支行帐号号码 */
public void setAccountNumber(int newNumber) {
setBranchAccountNumber( newNumber % getAccountNumberDivisor() ); setBranchNumber( newNumber / getAccountNumberDivisor() ); }
通过对常量使用获取函数,我们减少了代码出现问题的可能性,同时,提高了系统的可维护性。当帐号的格式变化时,我们知道它肯定会变化的,我们的代码很容易修改,因为它既有隐含的也有集中的供我们构造或分解帐号的必须信息。 集合的存取函数
存取函数的主要目的是将访问封装到字段,以减少代码的耦合。集合,如数组和矢量,要比单值复杂,实现起来自然不只是需要获取和设置成员函数。特别是因为要对集合进行增减,所以需使用存取成员函数。在集合字段的适当处加入如下存取成员函数:
成员函数类型 集合获取函数 集合设置函数 命名约定 getCollection() setCollection() 示例 getOrderItems() setOrderItems() 在集合中插入一个对象 从集合中删除一个对象 生成并且插入一个新对象到集合中 insertObject() deleteObject() newObject() insertOrderItem() deleteOrderItem() 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.用一个行内注释语句说明局部变量。行内注释是一种紧接在同一行的
命令代码后,用符号 // 标注出来的单行注释风格(它也叫“行末注