Files
OnJava8/docs/book/07-Implementation-Hiding.md
2019-06-18 14:03:54 +08:00

5.5 KiB
Raw Blame History

[TOC]

第七章 封装

访问控制(或者隐藏实现)与"最初的实现不恰当"有关。

所有优秀的作者——包括这些编写软件的人——都知道一件好的作品都是经过反复打磨才变得优秀的。如果你把一段代码置于某个位置一段时间,过一会重新来看,你可能发现更好的实现方式。这是重构的原动力之一,重构就是重写可工作的代码,使之更加可读,易懂,因而更易维护。

但是,在修改和完善代码的愿望下,也存在巨大的压力。通常,客户端程序员希望你的代码在某些方面保持不变。所以你想修改代码,但他们希望代码保持不变。由此引出了面向对象设计中的一个基本问题:"如何区分变动的事物和不变的事物"。

这个问题对于类库而言尤其重要。类库的使用者必须依赖他们所使用的那部分类库,并且知道如果使用了类库的新版本,不需要改写代码。另一方面,类库的开发者必须有修改和改进类库的自由,并保证客户代码不会受这些改动影响。

这可以通过约定解决。例如,类库开发者必须同意在修改类库中的一个类时,不会移除已有的方法,因为那样将会破坏客户端程序员的代码。与之相反的情况更加复杂。在有成员属性的情况下,类库开发者如何知道哪些属性被客户端程序员使用?这同样会发生在那些只为实现类库类而创建的方法上,它们也不是设计成可供客户端程序员调用的。如果类库开发者想删除旧的实现,添加新的实现,结果会怎样呢?任何这些成员的改动都可能破环客户端程序员的代码。因此类库开发者会被束缚,不能修改任何事物。

为了解决这一问题Java 提供了访问修饰符供类库开发者指明哪些对于客户端程序员是可用的,哪些是不可用的。访问控制权限的等级,从"最大权限"到"最小权限"依次是:publicprotected,包访问权限(没有关键字)和 private。根据上一段的内容,你可能会想,作为一名类库设计者,你会尽可能将一切都设为 private,仅向客户端程序员暴露你愿意他们使用的方法。这就是你通常所做的,尽管这与使用其他语言(尤其是 C编程和访问不受任何限制的人们的直觉相违背。

然而构建类库的概念和对类库组件的访问控制仍然不完善。其中仍然存在问题就是如何将类库组件捆绑到一个内聚到类库单元中。Java 中通过 package 关键字加以控制,类是在相同包下还是不同包下会影响访问修饰符。所以在这章开始,你将会学习如何将类库组件置于同一个包下,之后你就能明白访问修饰符的全部含义。

包的概念

包内包含一组类,它们被组织在一个单独的命名空间下。

例如,标准 Java 发布中有一个工具库,它被组织在 java.util 命名空间下。java.util 中含有一个类,叫做 ArrayList。使用 ArrayList 的一种方式是用其全名 java.util.ArrayList

// hiding/FullQualification.java

public class FullQualification {
    public static void main(String[] args) {
        java.util.ArrayList list = new java.util.ArrayList();
    }
}

这种方式使得程序冗长乏味,因此你可以换一种方式,使用 import 关键字。如果需要导入某个类,就需要在 import 语句中声明:

// hiding/SingleImport.java

import java.util.ArrayList;

public class SingleImport {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
    }
}

现在你就可以不加限定词,直接使用 ArrayList 了。但是对于 java.util 包下的其他类,你还是不能用。要导入其中所有的类,只需使用 ***** ,就像本书中其他示例那样:

import java.util.*

之所以使用导入,是为了提供一种管理命名空间的机制。所有类名之间都是相互隔离的。类 A 中的方法 f() 不会与类 B 中具有相同签名的方法 f() 冲突。但是如果类名冲突呢?假设你创建了一个 Stack 类,打算安装在一台已经有别人所写的 Stack 类的机器上,该怎么办呢?这种类名的潜在冲突,正是我们需要在 Java 中对命名空间进行完全控制的原因。为了解决冲突,我们为每个类创建一个唯一标识符组合。

到目前为止的大部分示例都只存在单个文件,并为本地使用的,所以尚未收到包名的干扰。但是,这些示例其实已经位于包中了,叫做"未命名"包或默认包。这当然是一种选择,为了简单起见,本书其余部分会尽可能采用这种方式。但是,如果你打算为相同机器上的其他 Java 程序创建友好的类库或程序时,就必须仔细考虑以防类名冲突。

一个 Java 源代码文件称为一个编译单元(有时也称翻译单元)。每个编译单元的文件名后缀必须是 .java。在编译单元中可以有一个 public 类,它的类名必须与文件名相同(包括大小写,但不包括后缀名 .java)。

访问权限修饰符

接口和实现

类访问权限

本章小结