2

关于封装的维基百科文章指出:

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

我在一个论坛上开始讨论封装,在其中我问你是否应该总是在 setter 和/或 getter 中克隆对象以保留上述封装规则。我认为,如果您想确保主对象内部的对象不会被主对象外部篡改,您应该始终克隆它。

一位讨论者认为,在这个问题上,您应该区分聚合和组合。基本上我认为他的意思是:

  • 如果要返回一个作为合成一部分的对象(例如,矩形的点),请克隆它。
  • 如果要返回一个属于聚合的对象(例如,作为 UserManager 的一部分的用户),只需在不破坏引用的情况下返回它。

这对我来说也很有意义。但现在我有点困惑。并想听听您对此事的看法。

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

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

4

2 回答 2

3

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

不,不是的。

您提到的人可能将对象状态的保护与对象的实现细节的保护混淆了。

记住这一点:封装是一种增加代码灵活性的技术。一个封装良好的类可以在不影响其客户的情况下更改其实现。这就是封装的本质。

假设以下类:

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 对象时,这些修改不会丢失。

我不想过多地扩展自己,但让我知道这是否清楚。我很乐意继续举一个更详细的例子。

于 2011-06-13T22:21:54.027 回答
0

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

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

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

于 2009-08-28T20:33:51.057 回答