Effective Java读书笔记-使类和成员的可访问性最小化

来源:互联网 发布:淘宝网铁观音 编辑:程序博客网 时间:2024/06/06 00:39

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

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

第一规则:

尽可能使每个类或者成员不被外界访问。

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

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

访问级别:

  • 私有的(private)——只要在声明该成员的顶层类的内部才可以访问这个成员。
  • 包级私有的(package-private)——声明该成员的包内部的任何类都可以访问这个成员。从技术角度讲,它被称为“缺省(default)访问级别”,如果没有成员指定访问修饰符,就采用这个访问级别。
  • 受保护的(protected)——声明该成员的类的子类可以访问这个成员,并且,声明该成员的包内部的任何类可以访问该成员。
  • 公有的(public)——在任何地方都可以访问该成员。

注意:当你仔细地设计了类的公有的API后,可能会觉得应该把所有其它的成员变为私有的。其实,只有当同一个包内的另一个类真正需要访问一个成员的时候,才应该删除private修饰符,使该成员变为包级私有的。如果你发现自己需要经常做这样的事情,就应该重新检查自己的系统设计,看看是否另一种分解方案所得到的类,与其它类之间的耦合度会更小。

私有成员和包级私有成员都是一个类的实现中的一部分,一般不会影响它的导出的API。如果这些类实现了Serializable接口,这些域就有可能被泄露到导出的API中。

受保护的成员应该尽量少使用。

限制方法访问性的规则:

如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。这样可以确保任何可以使用超类实例的地方都可以使用子类的实例。如果违反了这条规则,当编译子类的时候一定会产生错误的信息。如果一个类实现了一个接口,那么接口中所有类方法在这个类中也都必须被声明为公有的。之所以这样做是因为接口中的所有方法都隐含着公有访问级别。

有时为了便于测试,可以试着使类、接口或者成员更容易访问。这样做在一定程度上来说是好的。为了测试而将一个公有类的私有成员变为包级私有的,这还可以接受。但是如果将访问级别提高到超过了它,这就无法接受了。换句话说,不能为了测试而将类或接口变成包的导出的API的一部分。

实例域决不能是公有的

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

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

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

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

许多IDE会返回指向私有数据域的引用的访问方法,这样就会产生这个问题。修改这个问题通常有两个方法:

  1. 使公有数组变为私有的并增加一个公有的不可变列表。
private static final Thing[] PRIVATE_LISTS= {...};public static final List<Thing>values = Collections.unmodifiableList(Arrays.asList(PRIVATE_LISTS));
  1. 使数组变为私有的,并添加一个公有方法,返回私有数组的一个备份。
private static final Thing[] PRIVATE_LISTS = {...};public static final Thing[] values(){    return PRIVATE_LISTS.clone();}

应该尽可能的降低可访问性。在仔细的设计了一个最小的公有的API后,应当防止把任何散乱的类、接口和成员变为API的一部分。除了公有的静态的final域的特殊情形外,公有类都不应该包含公有域。并且要确保公有静态final域所引用的对象都是不可变的。

阅读全文
0 0
原创粉丝点击