封装聚合/组合

时间:2009-08-28 20:23:13

标签: clone encapsulation aggregation composition

关于封装状态的维基百科文章:

“封装还可以防止用户将组件的内部数据设置为无效或不一致的状态,从而保护组件的完整性”

我开始讨论关于论坛的封装问题,其中我询问是否应该始终在setter和/或getter中克隆对象以保留上述封装规则。我想,如果你想确保主对象内的对象没有被主要对象外部篡改,你应该总是克隆它。

一位讨论者认为,你应该在这个问题上区分聚合和构成。基本上我认为他是这样的:

  • 如果要返回作为合成一部分的对象(例如,矩形点),请将其克隆。
  • 如果要返回作为聚合一部分的对象(例如,用户作为UserManager的一部分),只需返回它而不破坏引用。

这对我也有意义。但现在我有点困惑。并希望对此事有自己的看法。

严格来说,包装总是要求克隆吗?

PS:我在PHP中编程,资源管理可能更具相关性,因为它是一种脚本语言。

2 个答案:

答案 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)

不,封装只是强制能力通过创建一个到该状态的访问点来控制状态。

例如,如果您想要封装的类中有一个字段,则可以创建一个公共方法,该方法将成为获取该字段包含的值的单一访问点。封装就是围绕该字段创建单个访问点的过程。

如果您希望更改 该字段的值的返回方式(克隆等),您可以自由地这样做,因为您知道您控制了该字段的单一途径。