封装的漏洞
?
我们都知道封装有很多优点,请看如下代码:
publicclass Man {
privateString name;
privateint age;
publicint getAge() {
returnage;
}
publicString getName() {
returnname;
}
publicvoid setAge(int i) {
If(age<=0)
return;
age= i;
}
publicvoid setName(String string) {
name= string;
}
}
?
如果,我们要将name拆分为firstname与lastname,对于访问器的代码,我们可以改写为如下:
public StringgetName() {
return firsstname+" "+lastname;
}
这样,除了改变类的方法外,不会影响其他代码,这样有利于降低程序的耦合性。
还有就是更改器可执行错误检查,不合条件的值可以不予处理。
比如,我们想设置age的值,其值必须大于0。我们可以在setAge方法里面做检查。
???????? 可是难道我们把类的实例域设置为私有的(private),然后提供其访问器与更改器,就真的实现了封装了吗?
???????? 请看如下代码:
public class Order{
privateMan man;
?
publicMan getMan() {
returnman;
}
?
publicvoid setMan(Man man) {
this.man= man;
}
?
}
该类中有个实例变量:引用Man的实例对象一个man。它的访问器方法返回的是一个Man对象。这样做,其实没有起到封装的作用。如:
Ordero=…;
Manman=o.getMan();
man.setName("mark");
System.out.println(o.getMan().getName());//输出mark。
这样就改变了原Order对象中实例域man的状态(一般类都有一组特定的实例域值,这些值的集合就是这个对象的当前状态),man中的name属性由原来的值变为了"mark"。很显然这是一个有漏洞的封装!!!
所以对于这种引用类型的实例域,我们应该改写其访问器方法为:
public Man getMan(){
return (Man)man.clone();
}
此时Man类的代码需要改写为:
?
publicclass Man implements Cloneable{//实现标记接口Cloneable。意味这该Man类可以克隆
privateString name;
privateDate birthday;
privateint age;
?
publicString getName() {
returnname;
}
publicvoid setName(String string) {
name= string;
}
publicMan(String name,Date d,int age) {
this.name= name;
this.birthday=d;
this.age=age;
}
/**
?* 覆盖Object中的clone()方法。
?*/
publicObject clone() throws CloneNotSupportedException {
Man cloned = (Man)super.clone();//浅克隆(不会克隆子对象,子对象需单独克隆)
cloned.birthday=(Date)birthday.clone();//克隆子对象
returncloned;
?
}
publicint getAge() {
returnage;
}
publicDate getBirthday() {
returnbirthday;
}
publicvoid setAge(int i) {
age= i;
}
publicvoid setBirthday(Date date) {
birthday= date;
}
}
?
这样就不会出现上面所说的封装漏洞了。
???????? 这里,顺便说一下final实例域。将实例域定义为final,构建对象时就必须初始化这样的域,也就是说,必须确保在每一个构造器执行之后,这个域的值被设置,且在后面的操作中,不能够再对它进行修改。
???????? final修饰符大都应用于基本数据,或者不可变类的域(如果类中的每个方法都不会改变其对象,这种类就是不可变的类,String类就是一个不可变的类)。对于可变的类,使用final修饰可能会对读者造成混乱。例如,
privatefinal date birthday;
?仅仅意味着存储在birthday变量中的对象引用在对象构造之后不能改变,而并不意味着birthday引用的Date实例对象是一个常量,任何方法都可以对birthday引用的Date对象调用setTime方法改变birthday引用的Date实例对象的状态。