严格来说,封装总是要求克隆吗?
不,不是的。
您提到的人可能将对象状态的保护与对象的实现细节的保护混淆了。
记住这一点:封装是一种增加代码灵活性的技术。一个封装良好的类可以在不影响其客户的情况下更改其实现。这就是封装的本质。
假设以下类:
class PayRoll {
private List<Employee> employees;
public void addEmployee(Employee employee) {
this.employees.add(employee);
}
public List<Employee> getEmployees() {
return this.employees;
}
}
现在,这个类的封装性很低。您可以说getEmployees方法破坏了封装,因为通过返回类型 List 您不能再更改此实现细节而不影响类的客户端。例如,我无法在不影响客户端代码的情况下为 Map 集合更改它。
通过克隆对象的状态,您可能会改变客户端的预期行为。这是解释封装的有害方式。
public List<Employee> getEmployees() {
return this.employees.clone();
}
可以说上面的代码改进了封装,因为现在 addEmployee 是唯一可以修改内部 List 的地方。因此,如果我有一个设计决定,将新的 Employee 项目添加到列表的头部而不是尾部。我可以做这个修改:
public void addEmployee(Employee employee) {
this.employees.insert(employee); //note "insert" is used instead of "add"
}
然而,这只是封装的一小部分,但价格却很高。您的客户得到的印象是可以访问员工,而实际上他们只有一份副本。因此,如果我想更新员工 John Doe 的电话号码,我可能会错误地访问 Employee 对象,期望更改会在下一次调用 PayRoll.getEmployees 时反映出来。
具有更高封装的实现将执行以下操作:
class PayRoll {
private List<Employee> employees;
public void addEmployee(Employee employee) {
this.employees.add(employee);
}
public Employee getEmployee(int position) {
return this.employees.get(position);
}
public int count() {
return this.employees.size();
}
}
现在,如果我想更改地图的列表,我可以自由地进行。此外,我并没有破坏客户可能期望的行为:当从 PayRoll 修改 Employee 对象时,这些修改不会丢失。
我不想过多地扩展自己,但让我知道这是否清楚。我很乐意继续举一个更详细的例子。