最小化类和成员的可访问性,使类和成员的可访问性最小化

发布时间:2019-10-04  栏目:编程  评论:0 Comments

Tips《Effective Java, Third
Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java
6,7,8,甚至9的发布,Java语言发生了深刻的变化。在这里第一时间翻译成中文版。供大家学习分享之用。书中的源代码地址:
9 API中的,所以JDK 最好下载 JDK 9以上的版本。但是Java 9
只是一个过渡版本,所以建议安装JDK 10。

区分良好设计与不良设计的组件的最重要的因素是,这个组件对其他组件隐藏它的内部数据和其他实现细节的程度。一个设计良好的组件隐藏了它的所有实现细节,干净地把它的API从它的实现中分离。然后组件仅仅可以通过它们的API通讯,而且不知道它们彼此的内部运作。这个概念,叫信息隐藏(information
hiding)
或者封装(encapsulation),是软件设计的一个基础原则[Parnas72]。

图片 1Effective
Java, Third Edition

信息隐藏在许多方面是重要的,大多数起源于这个事实:它解耦(decouples)了组成系统的组件,让它们可以开发、测试、优化、使用、理解和隔离修改。这加快了系统开发,因为组件可以并行地开发。它减轻了维护的负担,因为组件可以更快地理解、调试或者不要担心危害其他组件地替代。虽然信息隐藏和它本身并不能带来好的性能,但是它使得性能调优有效率:一旦一个系统完成了,性能分析决定了哪个组件造成了性能问题(条目67),这些组件可以没有影响其他组件正确的情形下优化。信息隐藏增加了软件复用,因为没有紧耦合的组件,除了它们被开发的情形下,常常证明在其他情形下是有用的。最后,信息隐藏减少了构建大系统的危险性,因为单个组件可以证明是成功的,即使系统并没有。

类和接口是Java编程语言的核心。它们是抽象的基本单位。该语言提供了许多强大的元素,可以使用它们来设计类和接口。本章包含指导原则,帮助你充分利用这些元素,使你的类和接口是可用的、健壮的和灵活的。

Java有协助信息隐藏的许多工具。访问控制(access control)机制[JLS, 6.6]
规定了类、接口和成员的访问性(accessibility)。实体的访问性由下面两者决定:它的声明位置和在声明时呈现哪个(如果有)访问标识符(private、protected和public)。这些修饰符正常使用对于信息隐藏是必要的。

将设计良好的组件与设计不佳的组件区分开来的最重要的因素是,组件将其内部数据和其他组件的其他实现细节隐藏起来。一个设计良好的组件隐藏了它的所有实现细节,干净地将它的API与它的实现分离开来。然后,组件只通过它们的API进行通信,并且对彼此的内部工作一无所知。这一概念,被称为信息隐藏或封装,是软件设计的基本原则[Parnas72]。

经验法则是简单的:使得每个类或者成员尽量不可访问。换句话说,使用与你编写软件的正常功能一致的尽可能低的访问等级。

信息隐藏很重要有很多原因,其中大部分来源于它将组成系统的组件分离开来,允许它们被独立地开发,测试,优化,使用,理解和修改。这加速了系统开发,因为组件可以并行开发。它减轻了维护的负担,因为可以更快速地理解组件,调试或更换组件,而不用担心损害其他组件。虽然信息隐藏本身并不会导致良好的性能,但它可以有效地进行性能调整:一旦系统完成并且分析确定了哪些组件导致了性能问题,则可以优化这些组件,而不会影响别人的正确的组件。信息隐藏增加了软件重用,因为松耦合的组件通常在除开发它们之外的其他环境中证明是有用的。最后,隐藏信息降低了构建大型系统的风险,因为即使系统不能运行,各个独立的组件也可能是可用的。

对于顶层的(非嵌套的)类和接口,有两个可能的访问等级:包私有的(package-private)公开的(public)。如果你声明了一个公开修饰符的顶层类或者接口,它是公开的;否则,它是包私有的。如果顶层的类或者接口是包私有的,它应该是这样。通过使得它是包私有的,你可以使得它是实现的一部分,而不是可以导出的API,而且你可以修改它、替代它,或者在下一部的发布中移除它,而不用担心已经存在的客户端。如果你使得它是公开的,那么你有义务为了维护兼容性而永久支持它。

Java提供了许多机制来帮助信息隐藏。 访问控制机制(access control
mechanism)[JLS,6.6]指定了类,接口和成员的可访问性。
实体的可访问性取决于其声明的位置,以及声明中存在哪些访问修饰符(private,protected和public)。
正确使用这些修饰符对信息隐藏至关重要。

如果是包私有的顶层类或者接口仅仅由一个类使用,考虑把顶层类变成是使用它的唯一类的私有静态嵌套类(条目24)。这减少了它的包里面的所有类对使用它的那个类的访问。但是相对于包私有的顶层类,减少一个不必要的公开类的访问重要得多:公开类是包的API,而包私有的顶层类已经是它实现的一部分。

经验法则很简单:让每个类或成员尽可能地不可访问。换句话说,使用尽可能低的访问级别,与你正在编写的软件的对应功能保持一致。

对于成员(域、方法、嵌套类和嵌套接口),有四种可能的访问等级,以增加访问性顺序列出如下:

对于顶层类和接口,只有两个可能的访问级别:包级私有(package-private)和公共的。如果你使用public修饰符声明顶级类或接口,那么它是公开的;否则,它是包级私有的。如果一个顶层类或接口可以被做为包级私有,那么它应该是。通过将其设置为包级私有,可以将其作为实现的一部分,而不是导出的API,你可以修改它、替换它,或者在后续版本中消除它,而不必担心损害现有的客户端。如果你把它公开,你就有义务永远地支持它,以保持兼容性。

  • 私有的(private) — 成员仅仅可以从在声明它的顶层类里访问

  • 包私有的(package-private)
    成员可以从在声明它的包里面的任何类访问。技术上被认为是默认的访问。如果没有指定访问修饰符(除了接口成员,它默认是公开的),这是你得到的访问等级。

  • 受保护的(protected)
    成员可以从声明它的类的子类访问(受到一些限制[JLS,
    6.6.2]),而且可以从声明它的包里面的任何类访问。

  • 公开的(public) — 成员可以从任何地方访问。

如果一个包级私有顶级类或接口只被一个类使用,那么可以考虑这个类作为使用它的唯一类的私有静态嵌套类。这将它的可访问性从包级的所有类减少到使用它的一个类。但是,减少不必要的公共类的可访问性要比包级私有的顶级类更重要:公共类是包的API的一部分,而包级私有的顶级类已经是这个包实现的一部分了。

在小心地设计你的类的公开API之后,你第一反应应该是使得所有其他的成员是私有的。只有在同一个包里面的另外一个类真正需要访问一个成员,你才应该移除私有修饰符而使得这个成员是包私有的。如果你发现自己经常这么做,你应该重新检测系统的设计,看看从另一个类解耦比较好的类是否需要另外一个分解。也就是说,私有的和包私有的成员是类实现的一部分,而通常不会影响它的导出API。然而,如果类实现了系列化(Serializable)(条目86和87),这些域可能泄漏到导出的API。

对于成员(属性、方法、嵌套类和嵌套接口),有四种可能的访问级别,在这里,按照可访问性从小到大列出:

对于公开类的成员,当访问等级从包私有到受保护时,访问性的急剧增加将会发生。访问的对象是类导出API的一部分,而且必须永远支持。而且,一个导出类的受保护的成员代表着实现细节的一个公开承诺(条目19)。受保护成员的需求是相等少见的。

  • private——该成员只能在声明它的顶级类内访问。
  • package-private——成员可以从被声明的包中的任何类中访问。从技术上讲,如果没有指定访问修饰符(接口成员除外,它默认是公共的),这是默认访问级别。
  • protected——成员可以从被声明的类的子类中访问(受一些限制,JLS,6.6.2),以及它声明的包中的任何类。
  • public——该成员可以从任何地方被访问。

有个重要的规则是,限制你的能力来减少方法的访问性。如果一个方法覆写了一个超类,在子类中它不能有比超类中更严格的访问等级[JLS,
8.4.8.3]。这是必要的,保证子类实例在超类实例可用的地方都可以使用(里氏代换原则(Liskov
substitution
principle)
,参考条目15)。如果你违反了这个规则,那么当你试着编译子类时编译器将会产生一个错误信息。这个规则的特例是,如果类实现了一个接口,所有接口中的类方法在类中必须声明为公开的。

在仔细设计你的类的公共API之后,你的反应应该是让所有其他成员设计为私有的。
只有当同一个包中的其他类真的需要访问成员时,需要删除私有修饰符,从而使成员包成为包级私有的。
如果你发现自己经常这样做,你应该重新检查你的系统的设计,看看另一个分解可能产生更好的解耦的类。
也就是说,私有成员和包级私有成员都是类实现的一部分,通常不会影响其导出的API。
但是,如果类实现Serializable接口,则这些属性可以“泄漏”到导出的API中。

为了方便测试你的代码,你可能倾向于让一个类、接口或者成员有比原本需要的更多访问性。这在某种程度是可以的。为了测试让一个公开类的私有成员成为包私有的,这是可接受的,但是再提高可访问性是不可接受的。换句话说,为了方便测试,让一个类、接口或者成员成为一个包导出API一部分,这是不可接受的。幸运的是,我们也没必要,因为测试可以作为需要测试包的一部分而运行,所以可以访问它的包私有元素。

对于公共类的成员,当访问级别从包私有到受保护级时,可访问性会大大增加。
受保护(protected)的成员是类导出的API的一部分,并且必须永远支持。
此外,导出类的受保护成员表示对实现细节的公开承诺。
对受保护成员的需求应该相对较少。

公开类的实例方法应该很少是公开的(条目16)。如果一个实例域是非final的或者是一个可变对象的引用,让它成为公开的,那么你放弃了限制存储在域中的值的能力。这意味着,这你放弃了实现这个域成为不变类的能力。而且,当类改变的时候,你放弃了采取任何行动的能力,所以有公开可变域的类通常不是线程安全的。即使一个域是final而且引用了一个可变对象,让它成为公开的,你就放弃了切换到一个新内部数据呈现(这个域不存在)的灵活性。

有一个关键的规则限制了你减少方法访问性的能力。
如果一个方法重写一个超类方法,那么它在子类中的访问级别就不能低于父类中的访问级别[JLS,8.4.8.3]。
这对于确保子类的实例在父类的实例可用的地方是可用的(Liskov替换原则,见条目
15)是必要的。 如果违反此规则,编译器将在尝试编译子类时生成错误消息。
这个规则的一个特例是,如果一个类实现了一个接口,那么接口中的所有类方法都必须在该类中声明为public。

同样的建议可以应用到静态域,但是有一个例外。你可以通过公开静态final域来暴露常量,假设这些常量是组成这个类提供的抽象的不可分割的一部分。按照惯例,这些域有大写字符的名字,单词用下划线分割(条目68)。这些域要么包含原始值,要么包含对不可变对象的引用(条目17)。一个包含可变对象引用的域,有非final域的所有缺点。虽然这个引用不可能改变,但是被引用的对象可以修改–有灾难性的结果。

留下评论

网站地图xml地图