Skip to content

Latest commit

 

History

History
63 lines (39 loc) · 10.2 KB

第15条:使类和成员的可访问性最小化 8111135b6def43f1837a4ce49e10087d.md

File metadata and controls

63 lines (39 loc) · 10.2 KB

第15条:使类和成员的可访问性最小化

区分一个组件设计的好不好,唯一重要的因素在于,它对于外部的其他组件而言,是否隐藏了其内部数据和其他实现细节。设计良好的组件会隐藏所有的实现细节,把API与实现清晰的隔离出来。然后,组件之间只通过API通信,一个模块不需知道其他模块的具体实现。这个概念被称为信息隐藏或**封装,**是软件设计的基本原则之一。

信息隐藏之所以非常重要有许多原因,其中大多是因为:它可以有效的解除组成系统的各个组件之间的耦合性,即解耦,使得这些组件可以独立的开发、测试、优化、使用、理解和修改。因为组件可以并行开发,所以加快了系统开发的速度,同时减轻了维护的负担,程序员可以更加快速的理解这些组件,并在调试它们的时候,不影响其他的组件。虽然信息隐藏本身无论是对内还是对外都不会带来更好的性能提升,但是它可以有效的调节性能:一旦完成一个系统,并通过剖析确定了哪些组件影响了系统性能,那些组件就可以进一步优化,而不会影响其他组件的正确性。

Java提供许多机制来协助信息隐藏。访问控制机制决定了类、接口和成员的可访问性。实体的可访问性是由该实体声明所在的位置,以及该实体声明中所出现的访问修饰符(private,protected,public)共同决定的。正确的使用这些修饰符对于实现信息隐藏是非常关键的。

规则很简单:尽可能的是每个类或者成员不被外界访问。换句话说,应该使用与你正在编写的软件的对应功能相一致的、尽可能最小的访问级别。

对于顶层的类(类只要不是内部类就可以说是顶层类)和接口,只有两种可能的访问级别:包级私有(package-private)和公有的(public)。如果你用public修饰符声明了顶层类或者接口,那它就是公有的;否则它将是包级私有的。如果类或者接口被做成了包级私有的,它就应该是包级私有的。通过把类或者接口做成包级私有的,它实际上成了这个包的实现的一部分,而不是该包导出的API的一部分,在以后的发行版本中,可以对它进行修改、替换或者删除,而无须担心会影响到现有的客户端程序。如果把它做成公有的,你就有责任永远支持它,以保持它们的兼容性。

如果一个包级私有的顶层类(或者接口)只是在有一个类的内部被用到,就应该考虑使它成为唯一使用它的那个类的私有嵌套类(私有内部类)。这样可以将它的可访问范围从包中的所有类缩小到使用它的那个类。然而,降低不必要的公有类的可访问性,比降低包级私有的顶层类的可访问性重要的多:因为公有类是包的API的一部分,而包级私有的顶层类则已经是这个包的实现的一部分。

对于成员(域,方法,嵌套类和嵌套接口)四种可能的访问级别,下面按照可访问性的递增顺序罗列出来:

  • 私有的(private)——只有在声明该成员的顶层类内部可以访问这个成员。
  • 包级私有的(package-private)——声明该成员的内部的任何类都可以访问这个成员。从技术上讲,它被称为“缺省”访问级别,如果没有为成员指定访问修饰符,就采用这个访问级别(当然,接口成员除外,它们默认的访问级别是公有的)。
  • 受保护的(portected)——声明该成员的类的子类可以访问这个成员,并且声明该成员的包内部的任何类也可以访问这个成员。
  • 公有的(public)——在任何地方都可以访问该成员。

当你仔细的设计了类的公有API之后,可能觉得应该把所有其他的成员都变成私有的。其实,只有当同一个包内的另一个类真正需要访问一个成员的时候,你才应该删除private修饰符,使该成员变成包级私有的。如果你发现自己经常要做这样的事情,就应该重新检查系统设计,看看是否另一种分解方案所得到的类,与其他类之间的耦合度会更小。也就是说,私有成员和包级私有成员都是一个类的实现中的一部分,一般不会影响导出的API。然而,如果这个类实现了Serializable接口,这些域就有可能会被“泄漏”到导出的API中。

对于公有类的成员,当访问级别从包级私有变成保护级别时,会大大增强可访问性。受保护的成员是类的导出的API的一部分,必须永远得到支持。导出的类的受保护成员也代表了该类对于某个实现细节的公开承诺。应该尽量少用受保护的成员。

有一条规则限制了降低方法的可访问性的能力。如果方法覆盖了超类种的一个方法,子类种的访问级别就不允许超过类种的访问级别。这样可以确保任何可使用的超类的实例的地方也都可以使用子类的实例(里氏替换原则)。如果违反了这条规则,那么当你试图编译该子类的时候,编译器就会产生一条错误消息。这条规则又一个特例:如果一个类实现了一个接口,那么接口中所有的方法在这个类中也都必须被声明为公有的。

为了便于测试代码,你可以试着使类、接口或者成员变得更容易访问。这么做在一定程度上来说是好的,为了测试而将一个公有的私有成员变成包级私有的,这还可以接受,但是要将访问级别提高到超过了它,这就无法接受了。换句话说,不能为了测试,而将类、接口或者成员包的导出的API的一部分。幸运的是,也没有必要这么做,因为可以让测试作为被测试的包的一部分来运行,从而能够访问它的包级私有的元素。

公有类的实例域绝不能是公有的。如果实例域是非final的,或者是一个指向可变对象的final引用,那么一旦使这个域成为公有的,就等于放弃了对存储在这个域中的值进行限制的能力;这意味着,你也放弃了强制这个域不可变的能力。同时,当这个域被修改的时候,你也失去了对它采取任何行动的能力。因此,包含公有的可变域的类通常是线程不安全的。即使域是final的,并且应用了不可变的对象,但当把这个域变成公有的时候,也就放弃了“切换到一种新的内部数据表示法”的灵活性。

这条建议同样适合静态域,只是一种情况例外。假设常量构成了类提供的整个抽象的一部分,可以通过公有的静态final域来暴露这些常量。按照惯例这种域的名称由大写字母组成,单词之间用下划线隔开。这一点很重要,这些域要么包含基本类型的值,要么包含指向不可变对象的引用。如果final域包含可变对象的引用,它便具有非final域的所有缺点。虽然引用本身不能被修改,但是它所引用对象却可以被修改,这会导致灾难性的后果。

注意,长度非零的数组总是可变的,所以让类具有的静态final数组域,或者返回这种域的访问方法,这是错误的。如果类具有这样的域或者访问方法,客户端将能够修改数组中的内容。这是安全泄漏的一个常见根源。

public static final Thing[] VALUES = { ... };

要注意,许多IDE产生的访问方法会返回指向数组域的引用,正好导致了这个问题。修正这个问题有两种方法。可以使数组变成私有的,并增加一个公有的不可变列表:

private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES = 
													Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES))

另一种方法使,将数组变为私有的,并添加一个公有方法,它放回私有数组的一个拷贝:

private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values(){
	return PRIVATE_VALUES.clone();
}

从Java 9开始,又新增了两种隐式的访问级别,作为系统模块的一部分。一个模块就是一组包,就像一个包是一组类一样。模块可以通过其模块声明中的导出声明显示的导出它的一部分包(按照惯例,这包含在名为module-info.java的源文件中)。模块中未被导出的包在模块之外是不可访问的;在模块内部,可访问性不受导出声明的影响。使用模块系统可以在模块内部的包之间共享类,不用让他们对全世界都可见。未导出的包中公有类的公有成员和受保护的成员都提高了两个隐式访问级别,这是正常的公有和受保护级别在模块内部的对等体。对于这种共享的需求相对罕见,经常通过在包内部重新安排类来解决。

与四个主访问级别不同的是,这两个基于模块的级别主要提供咨询。如果把模块的JAR文件放在应用程序的类路径下,而不是放在模块路径下,模块中的包就会恢复其非模块的行为:无论包是否通过模块导出,这些包中公有类的所有公有的和保护的成员将都又正常可访问性。严格执行新引入的访问级别的一个示例是JDK本身:Java类库中未导出的包在其模块之外确实是不可访问的。

对于传统的Java程序员来说,不仅受限于工具的模块提供了访问保护,而且在本质上主要也是提供咨询。为了利用模块这一特性,必须将包集中到模块中,并在模块声明中显示的表明所有的依赖关系,重新安排代码树结构,从模块内部采取特殊的动作调节对于非模块化的包的任何访问。现在说模块将在JDK本身之外获得广泛的使用,还为时过早。同时,似乎最好不用它们,除非你的需要非常迫切。

总而言之,应该始终尽可能(合理)地降低程序元素的可访问性。在仔细的设计了一个最小的公有API之后,应该防止把任何散乱的类、接口或者成员变成API的一部分。除了公有静态的final与的特殊情形之外,公有类都不应该使用公有域,并且要确保公有静态final域所引用的对象都是不可变的。