维基百科关于封装状态的文章:
“封装还可以防止用户将组件的内部数据设置为无效或不一致的状态,从而保护组件的完整性”
我在论坛上开始了关于封装的讨论,其中我询问是否应该始终在 setter 和/或 getter 内克隆对象,以保留上述封装规则。我认为,如果您想确保主对象内部的对象不会在主对象外部被篡改,您应该始终克隆它。
一位讨论者认为,在这个问题上应该区分聚合和组合。基本上我认为他的意思是这样的:
- 如果您想返回属于组合一部分的对象(例如,矩形的点),请克隆它。
- 如果您想返回属于聚合一部分的对象(例如,作为 UserManager 一部分的 User),只需在不破坏引用的情况下返回它即可。
这对我来说也很有意义。但现在我有点困惑了。并想听听您对此事的看法。
严格来说,封装是否总是要求克隆?
PS:我用 PHP 编程,其中资源管理可能更相关,因为它是一种脚本语言。
最佳答案
Strictly speaking, does encapulation always mandate cloning?
不,事实并非如此。
您提到的人可能混淆了对象状态的保护与对象实现细节的保护。
请记住这一点:封装是一种提高代码灵活性的技术。封装良好的类可以更改其实现而不影响其客户端。这就是封装的本质。
假设有以下类:
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 的地方。因此,如果我有一个设计决定,将新的员工项目添加到列表的头部而不是尾部。我可以做这样的修改:
public void addEmployee(Employee employee) {
this.employees.insert(employee); //note "insert" is used instead of "add"
}
然而,这只是封装的一小部分增量,但代价却很大。您的客户给人的印象是可以接触到员工,但实际上他们只有一份副本。因此,如果我想更新员工 John Doe 的电话号码,我可能会错误地访问 Employee 对象,并期望在下次调用 PayRoll.getEmployees 时反射(reflect)更改。
具有更高封装性的实现将执行以下操作:
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();
}
}
现在,如果我想更改 map 列表,我可以自由地进行。 此外,我并没有破坏客户可能期望的行为:当从 PayRoll 修改 Employee 对象时,这些修改不会丢失。
我不想过多地扩展自己,但请告诉我这是否清楚。我很乐意继续提供更详细的示例。
关于clone - 封装聚合/组合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1349142/