JAVA的访问控制3-封装和可访问范围
在前文JAVA的访问控制1-访问控制的必要性,包和JAVA的访问控制2-public,protected,private和package-private中我们介绍了访问控制的由来,基本概念以及具体内容,这一篇会继续深入一些,讲解一下这几篇文章最开始提到的封装与可访问范围的一些内容。
封装
JAVA语言通过访问控制符,给我们提供了一种能力,我们可以控制客户程序员的访问范围,或者说,客户程序员可以只知道这个类可以完成什么,而不需要去关注其实现细节。这样的话,类的创建者就可以将内部的机制隐藏起来,并且,在需要修改的时候,可以放心的修改内部机制,不用担心破坏客户程序员的使用。
封装就是这种能力,有时候这个能力也叫信息隐藏.
封装不只是JAVA语言中可用的一种概念,它是软件设计的一个基本原则之一。封装在软件设计的很多层次都有体现。
在最小的类层次,封装体现为类对外提供一些方法,内部存在自己的实现。
在更大的模块层次,封装体现为整个模块对外提供了哪些能力,外部只能使用这些能力,而不必深入了解模块内部的机制,比如用户模块可能通过一些方法对外提供给了人员的管理能力,其他模块只需要使用这些方法即可,而不必自行去操作用户模块的数据库等内部内容。
在服务层次,体现为整个服务对外提供了哪些能力,一般是通过接口的方式提供,而内部实现(比如数据库)等内容,则不会对外提供。
可以看到,每一层的内容不一样,但是思想是完全一致的,就是把内部很小心的隐藏起来,只对外提供必要的方法,而不是把整个系统全部敞开。
举个反例,如果如果我们要使用某个类,需要提前设置这个类的某个内部变量,然后再用某些方法,那这个就是没有封装的,如果其作者想要修改前面的那个内部变量,势必会影响客户端,导致更大范围的修改,这样的系统就会盘根错节,牵一发而动全身,最终难以维护。
这种就是紧密耦合,会导致软件很难以维护,通过封装可以有效缓解这个问题,做到解耦。
除此以外,封装还可以提高复用性,如果这个类很易于使用,并且提供的功能又很明确,那么其它遇到这一需求的就可以直接使用。如果类封装的不好,使用起来困难重重,很可能会导致其他人不愿意使用,还不如自己写一个新的,复用性也就很差了。
封装与可访问范围
上面介绍了封装和封装可以带来的好处,为了更好的封装,我们需要控制可访问范围。关于控制可访问范围,只有一个原则,那就是,保持可访问范围最小。我们要尽可能的做到,每个内部的变量和方法,都不能被外部访问,对外开放的,也就是设置为public
的,仅应是那些必须对外提供的能力。
如果一个方法被设置成public
了,那就意味着我们对外做出了一个承诺,public
方法的名称,入参,返回值等,一旦发布就难以修改了,后续还需要持续维护它的兼容性,务必谨慎。
类的实例变量,不应该设置为public
的,如果需要对外提供,那么可以提供getXXX
的getter
方法。
另外,如果类的实例变量是可变对象,比如数组,列表,或者是Date
这种可变的类,在提供getter
方法的时候,要注意保护自己。
内部的变量提供给外部,很可能会被外部篡改,最好是变量本身设置为不可变,或者是克隆一个克隆体给外部,这样外部的类就无法修改类的内部变凉了。出门在外,要当心。
下面举一个例子说明这种情况:
public class XxxService {
private static final String[] FIELDS = {"field1", "field2"};
public String[] getFields() {
return FIELDS;
}
}
这里是一个类,它的内部有一个FIELDS
常量,按照定义private static final String[]
,原意应该是一个不可变的数组,但是使用方没有注意到,而是对它进行了修改:
// 非法使用者:
XxxService xxxService = new XxxService();
String[] fields = xxxService.getFields();
fields[0] = "myField";
// ...
这之后XxxService
所有依赖FIELDS
的方法都会被破坏,问题还隐藏的很深,并不在XxxService
中,难以排查。
很多安全问题也是这样子来的,请务必小心。
要规避也比较简单,返回这个数组的副本就可以了:return Arrays.copyOf(FIELDS, FIELDS.length)
,这样子使用方即使修改了返回的数组,也就不会对XxxService
的功能有影响了。