考虑到对象封装,getter应该返回一个不可变的属性吗?

时间:2008-09-22 10:57:30

标签: java oop

当getter返回一个属性时,例如返回List其他相关对象,该列表及其对象是不可变的,以防止类外的代码,更改这些对象的状态,而不是主父对象知道吗?

例如,如果Contact对象有一个getDetails getter,它返回ListContactDetails个对象,那么任何调用该getter的代码:

  1. 可以在ContactDetail对象不知道的情况下从该列表中删除Contact个对象。
  2. 可以更改每个ContactDetail对象,而Contact对象不知道它。
  3. 那我们该怎么办呢?我们应该只信任调用代码并返回容易变化的对象,还是努力为每个可变类创建一个不可变类?

11 个答案:

答案 0 :(得分:6)

这是一个问题,你是否应该在你的代码中“防守”。如果您是您班级的(唯一)用户并且您相信自己,那么无论如何都不需要不可变性。但是,如果此代码无论如何都需要工作,或者您不信任您的用户,那么将外部化的所有内容都变为不可变。

也就是说,我创建的大多数属性都是可变的。偶尔的用户会把这种情况搞得一团糟,但这又是他/她的错,因为有明确证明突变不应该通过吸气剂接收的可变对象发生。

答案 1 :(得分:6)

这取决于具体情况。如果列表是可变的,那么当List拥有一个非常好的API时,没有必要用主要类的API来混淆它。

但是,如果主类无法应对突变,那么您将需要返回一个不可变列表 - 列表中的条目本身也可能需要是不可变的。

不要忘记,您可以返回一个自定义List实现,该实现知道如何安全地响应变异请求,无论是通过触发事件还是直接执行任何所需的操作。事实上,这是使用内部类的好时机的典型例子。

答案 2 :(得分:5)

如果您掌握了调用代码,那么最重要的是您所做的选择会在所有正确的位置记录下来。

答案 3 :(得分:3)

在Java中使用Collection,List,Set或Map的特定情况下,使用return Collections.unmodifiableList(list);

可以很容易地将不可变视图返回给类

当然,如果仍然可以修改后备数据,那么您需要制作列表的完整副本。

答案 4 :(得分:3)

Joshua Bloch在他出色的“Effective Java”一书中说,在返回这样的东西时,你应该总是制作防御性副本。这可能有点极端,特别是如果ContactDetails对象不是Cloneable,但它始终是安全的方式。如果有疑问总是支持代码安全性而不是性能 - 除非分析表明克隆是一个真正的性能瓶颈。

实际上可以添加几种级别的保护。您只需返回该成员,这实际上是为您的类的内部提供任何其他类访问权限。非常不安全,但公平地做了很多。如果要更改内部,以便将ContactDetails存储在Set中,它也会在以后引起麻烦。您可以返回一个新创建的列表,其中包含对内部列表中相同对象的引用。这样更安全 - 另一个类无法删除或添加到列表中,但它可以修改现有对象。第三,返回一个新创建的列表,其中包含ContactDetails对象的副本。这是安全的方式,但可能很昂贵。

我会这样做更好。根本不要返回列表 - 而是返回迭代器列表。这样您就不必创建新列表(List有一个获取迭代器的方法),但外部类不能修改列表。它仍然可以修改项目,除非您编写自己的迭代器,根据需要克隆元素。如果稍后在内部切换到使用另一个集合,它仍然可以返回迭代器,因此不需要进行外部更改。

答案 5 :(得分:2)

真的取决于背景。但一般来说,是的,应该尽可能写出防御性代码(返回数组副本,返回 readonly包装器集合等)。无论如何,它应该明确记录

答案 6 :(得分:1)

我以前只返回列表的只读版本,或者至少是一份副本。但是列表中包含的每个对象都必须是可编辑的,除非它们在设计上是不可变的。

答案 7 :(得分:1)

我想你会发现每个gettable都是不可变的非常罕见。

您可以做的是在此类对象中更改属性时触发事件。也不是一个完美的解决方案。

文档可能是最实用的解决方案;)

答案 8 :(得分:1)

您的首要任务应该是遵守得墨忒耳法则或“告诉不要问”;告诉对象实例该怎么做,例如。

contact.print( printer ) ;  // or
contact.show( new Dialog() ) ; // or
contactList.findByName( searchName ).print( printer ) ;

面向对象的代码告诉对象做事。程序代码获取信息然后对该信息起作用。要求一个对象揭示其内部细节打破封装,它是程序代码,而不是声音OO编程,而Will已经说它是一个有缺陷的设计。

如果遵循Demeter法的方法,对象状态的任何变化都会通过其定义的界面发生,因此副作用是已知和受控的。你的问题就消失了。

答案 9 :(得分:0)

当我开始时,我仍然受到隐藏您的数据OO PRINCIPALS LOL的影响。如果有人改变了属性暴露的一个对象的状态,我会坐下来思考会发生什么。我应该只为外部来电者阅读吗?我根本不应该暴露它们吗?

收藏品将这些焦虑带到了极致。我的意思是,当我不看的时候,有人可以删除集合中的所有对象!

我最终意识到,如果你的对象对其外部可见属性及其类型持有如此严格的依赖性,如果有人在一个不好的地方接触它们你就会兴旺发达,那么你的体系结构是有缺陷的。

有正当理由使您的外部属性只读并且其类型不可变。但这是个案,不是典型的案例,imho。

答案 10 :(得分:0)

首先,setter和getter表示OO不好。一般来说,OO的想法是你要求对象为你做点什么。设置和获取是相反的。 Sun应该已经找到了实现Java bean的其他方法,以便人们不会选择这种模式并认为它是“正确的”。

其次,你拥有的每个对象本身都应该是一个世界 - 通常,如果你要使用setter和getters,它们应该返回相当安全的独立对象。这些对象可能是也可能不是不可变的,因为它们只是一等对象。另一种可能性是它们返回始终不可变的本机类型。所以说“定居者和吸气者应该归还一些不可改变的东西”并没有多大意义。

作为使不变对象本身,你应该几乎总是让成员你的对象最终内,除非你有很强的理由不(最终应该是默认的,“可变”应该是,将覆盖默认关键字)。这意味着只要有可能,对象就是不可变的。

对于你可能传递的预定义的准对象事物,我建议你用自己的方法将诸如集合和值组之类的东西一起包装到他们自己的类中。我几乎从不传递一个不受保护的集合,因为你没有提供任何指导/帮助,如果使用精心设计的对象应该是显而易见的。安全性也是一个因素,因为允许某人访问您班级内的集合,几乎不可能确保该班级始终有效。