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

术语:

封装(encapsulation)或者称为“信息隐藏(information hiding)”:模块隐藏所有的实现细节,把它的API与它的实现清晰的隔离开来,模块之间的通信只通信它们的API进行,一个模块不需要知道其他模块内部工作情况。



        信息隐藏很重要,因为它可以有效地解除组成系统的各模块之间的耦合关系,便利这些模块可以独立的开发、测试、优化、使用、理解和修改。这样可以加快系统开发的速度,因为这些模块可以并行开发。同时也减轻了维护的负担,因为程序员可以更快地理解这些模块,并且在调试它们的时候可以不影响其他的模块,虽然信息隐藏本身无论是对内还是对外都不会带来更好的性能,但是它可以有效地调节性能:一旦完成一个系统,并通过剖析确定了哪些模块影响了系统的性能,那些模块就可以被进一步的优化,而不会影响其他模块的正确性,也就是说模块化了系统的各功能组件之后,可以在日后的工作中完美某一个模块来提升性能而不必牵涉到其他模块,这就是松耦合的一个优势。同时,也提升了模块的可重用性,因为模块之间并不紧密相连,除了开发这些模块所使用的环境外,它们在其他的环境中往往也很有用。最后,信息隐藏也降低了构建大型系统的风险,因为即使整个系统不可用,但是这些独立的模块却有可能是可用的。

        Java中的访问控制机制(access control)为信息隐藏提供了重要保障,它决定了类、接口和成员的可访问性(accessibility)。实体的可访问性由该实体声名所在的位置,以及实体声名中所出现的访问修饰符(private、protected和public或者是默认的)共同决定。

        最基本的规则是:尽可能使每个类或者成员不被外界访问。也就是尽可能将访问级别降到最低。

        对于顶层的(非嵌套的)类和接口,只有两种可能的访问级别:包级私有(package-private)和公有的(public)。如果你用public修饰符声名了顶层类和接口,那它就是公有的,否则它将是包级私有的。如果类或者接口能够被做成包级私有的,它就应该被做成包级私有的,通过把类或者接口做成包级私有的使它实际上成了这个包的实现的一部分,而不是该包导出的API的一部分,如果将来需要对其进行修改、替换、或者删除,无需担心会影响到现有的客户端,因为客户端用的只是导出的API而与具体的实现并无紧密关联。但是,如果将它做成了公有的,那么它就变成了API的一部分,开发者就有责任永远支持它,以保持它们的兼容性。如果一个包级私有的顶层类或接口只是在某一个类的内部被用到,那就应该考虑将它做成特定类的内部类,这样可以将它的可访问范围从包中的所有类缩小到使用它的那个类。然而,降低不必要的仅有类的可访问性比降低包级私有的顶层类要更重要,因为公有类是包的API的一部分,是客户端要真切使用的一部分,而包级私有的顶层类则已经是这个包的实现的一部分了,总之,“和自己打交道总要和别人打交道容易的多。”

        对于成员(包括域、方法、嵌套类和嵌套接口)有四种可能的访问级别:

        1、私有的(private):只有在声明该成员的顶层类内部才可以访问这个成员。

        2、包级私有的(package-private):声明该成员的包内部的任何类都可以访问这个成员。这也被称为“缺省(default)访问级别”,如果没有为成员指定访问修饰符,就采用这个访问级别。

        3、受保护的(protected):声明该成员的类的子类可以访问这个成员,并且,声名该 成员的包内部的任何类也可以访问这个成员。

        4、公有的(public):在任何地方都可以访问该成员。

        可以看出,上面的四种访问级别是的约束是由强到弱的,当仔细设计了类的仅有API后,可能觉得应该把所有其他的成员都变成私有的。其实只有当同一个包内的另一个类真正需要访问一个成员的时候,你才应该删除private修饰符,使该成员变成包级私有的。如果你发现了自己经常需要这么做,那么就应该重新检查一下系统设计,看看是有另一种分解方案所得到的类可以使其他类之间的耦合度更小。

        私有成员和包级私有成员都是一个类的实现的一部分,一般不会影响到它的导出的API,然而如果这个类实现了Serializable接口,那么这些域就有可能会被泄露到导出的API中。对于仅有类的成员,当访问级别从包级私有变成保护级别的时候,会大大增强可访问性。受保护的成员是类的导出API的一部分,但这同时也带来了要在将来考虑兼容API的麻烦,导出类的受保护成员也代表了该类对某个实现细节的公开承诺。受保护的成员应该尽量少用。

        如果方法覆盖了超类中的一个方法,那么子类中的访问级别不允许低于超中的访问级别,这样可以确保任何使用盐类的实例的地方也都可以使用子类的实例,如果违反了这条规则那么当试图编译该子类的时候,编译器就会产生错误。这条规则有一个特殊情况:如果一个类实现了一个接口,那么接口中所有的方法在这个类中都必须声名为公有的,因为接口中的所有方法都隐含着公有访问级别。同时,不能为了测试而将类、接口或者成员变量变成包的导出API的一部分,同时这也是没有必要的,可以将测试实现了被测试包的一部分,这样它就有了访问包级私有的元素的权限。

        实例域是不能公有的。如果域是非final的,或者说是指向一个可变对象的(如数组)final引用,那么这将意味着放弃了对存储在这个域中的值进行限制的能力,也放弃了强制这个域不可变的能力。财时,当这个裁员被修改的时候,也推动了对它采取任何行动的能力。因此,包含可变域的类并不是线程安全的,即使域是final的,并且引用的不可变对象,当所这个域变成仅有的时候,也就放弃了“切换到一种新的内部数据表示法”的灵活性,因为你已经把它暴露给了客户端,成为了客户端可访问的部分。对于静态域也有以上的问题,但是如果常量构成了类提供的整个抽象中的一部分,可以通过仅有的静态final域来暴露这些常量。这种域的名子由大写字母组成,单词之间用下划线隔开。以下是来自java.math的实例

    /**
     * The {@code double} value that is closer than any other to
     * <i>e</i>, the base of the natural logarithms.
     */
    public static final double E = 2.7182818284590452354;

    /**
     * The {@code double} value that is closer than any other to
     * <i>pi</i>, the ratio of the circumference of a circle to its
     * diameter.
     */
    public static final double PI = 3.14159265358979323846;

         很重要的一点是这些域要么包含基本类型的值,要么包含指向不可变对象的引用。如果final域包含了可变对象的引用,那么它有非final域的所有缺点:虽然引用本身不能被修改,但是它所引用的对象却是可以修改的。

        长度非零的数组总是可变的,注意如下代码:

	// Potential security hole!
	public static final Thing[] VALUES = { ... };
        要注意许多IDE会产生返回指向私有数组域的引用的访问方法,这样就会产生这个问题,一般修正这个问题有两种方法, 可以使仅有数组变成私有的,并增加一个仅有的不可变列表:

private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES = 
			Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
        其中帮助方法Collections.unmodifiableList限制了List为不可改变的。 另外一种方法是使数组变成私有的,并且添加一个仅有的方法,它返回私有数组的一个备份。

       

	private static final Thing[] PRIVATE_VALUES = { ... };
	public static final Thing[] values() {
		return PRIVATE_VALUES.clone();
	}
        总而言之,应该始终尽可能地降低可访问性。在仔细设计了一个最小的公有API之后,应该防止把任何散乱的类、接口和成员变成API的一部分。除了仅有静态final域的特殊情形之外,公有类都不应该包含公有域。并且要确保公有静态final域所引用的对象都是不可变的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值