关于封装状态的维基百科文章:
“封装还可以防止用户将组件的内部数据设置为无效或不一致的状态,从而保护组件的完整性”
我开始讨论关于论坛的封装问题,其中我询问是否应该始终在setter和/或getter中克隆对象以保留上述封装规则。我想,如果你想确保主对象内的对象没有被主要对象外部篡改,你应该总是克隆它。
一位讨论者认为,你应该在这个问题上区分聚合和构成。基本上我认为他是这样的:
这对我也有意义。但现在我有点困惑。并希望对此事有自己的看法。
严格来说,包装总是要求克隆吗?
PS:我在PHP中编程,资源管理可能更具相关性,因为它是一种脚本语言。
答案 0 :(得分:3)
严格来说,包装总是要求克隆吗?
不,它没有。
你提到的那个人可能会混淆保护对象的 state 并保护对象的实现细节。
请记住:封装是一种增加代码灵活性的技术。封装良好的类可以更改其实现而不会影响其客户端。这是封装的本质。
假设以下类:
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项添加到List的头部而不是尾部。我可以做这个修改:
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对象时,这些修改不会丢失。
我不想过多地扩展自己,但如果这是明确的话,请告诉我。我很乐意继续更详细的例子。
答案 1 :(得分:0)
不,封装只是强制能力通过创建一个到该状态的访问点来控制状态。
例如,如果您想要封装的类中有一个字段,则可以创建一个公共方法,该方法将成为获取该字段包含的值的单一访问点。封装就是围绕该字段创建单个访问点的过程。
如果您希望更改 该字段的值的返回方式(克隆等),您可以自由地这样做,因为您知道您控制了该字段的单一途径。