2008-09-22 28 views
18

当一个getter返回一个属性,比如返回一个List其他相关对象时,如果该列表和它的对象是不可变的,以防止类外的代码,改变这些对象的状态,而不知道主父对象?考虑到对象封装,应该让getters返回一个不可变属性?

例如,如果一个Contact对象,具有getDetails吸气剂,它返回ContactDetailsList一个对象,那么任何码调用该吸气剂:

  1. 可以从该列表中ContactDetail对象而不Contact对象知道的它。
  2. 可以更改每个ContactDetail对象而不知道对象。

那么我们应该在这里做什么?我们是否应该相信调用代码并返回容易变化的对象,或者坚持不懈地为每个可变类创建一个不可变的类?

回答

7

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

也就是说,我创建的大多数属性都是可变的。一个偶尔的用户把它弄糊涂了,但是这又是他/她的错,因为它清楚地记录了通过获取器接收到的可变对象不应该发生变异。

+3

这是一个有趣的区别 - 天气或不是你自己的代码。我永远不会想到我写作安全的课程以外的任何东西。我不相信编程有什么优势,假设任何人给你打电话总是会得到正确的结果......如果在你的职业生涯中花费了20或30分钟的时间,那么跳过几行打字实际上可能会更快一些调试?键入这些额外的行几乎是免费的,而不是花在项目上的时间。 – 2009-04-20 23:35:53

5

如果您可以控制调用代码,那么最重要的是您所做的选择在所有正确的位置均有记录。

1

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

2

实际上取决于上下文。但一般来说,是的,应该尽可能地写作防御性代码(返回阵列副本,返回只读集合等的包装)。无论如何,它应该是明确记录的

1

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

你可以做的是在属性在这些对象内改变时触发事件。不是一个完美的解决方案。

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

6

这取决于上下文。如果列表的目的是可变的,当List有一个非常好的API时,使用变异的方法来干扰主类的API是毫无意义的。

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

不要忘记,虽然,你可以返回,知道如何通过是否触发事件或直接执行任何必要的行动安全应对突变的请求,自定义列表实现。事实上,这是一个使用内部类的好时机的典型例子。

0

当我开始了我仍然在很大程度上LOL的隐藏数据OO校长的影响下。我会坐下来思考,如果有人改变了某个财产暴露的对象的状态会发生什么。我应该让他们只读外部呼叫者?我应该不要暴露他们?

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

我最终意识到,如果你的对象在他们的外部可见的性质和类型等保持紧密的依赖关系,如果有人在一个不好的地方你去轰触动他们,你的架构是有缺陷的。

有正当的理由,让您的只读外部属性和它们的类型不变。但是,这是一个非常典型的案例,而不是典型的案例。

+0

数据隐藏不仅仅是为了防止其他对象改变你的变量 - 这是关于确保你的课程的用户不会受到内部表示。公开你的内部也会阻止你在成员修改时添加要完成的操作。 – DJClayworth 2008-09-30 14:08:19

1

你的当务之急应该是有规律可循的德米特或“告诉不问”;告诉对象实例做什么,例如

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

面向对象的代码告诉对象做事情。程序代码获取信息然后作用于该信息。要求一个对象揭示其内部细节会破坏封装,这是程序代码,而不是完美的面向对象编程,而威尔已经说过这是一个有缺陷的设计。

如果按照迪米特法则接近中的对象的状态的任何变化发生通过其定义的接口,因此副作用是公知的和控制。你的问题消失了。

3

在收集,列表,设置或地图在Java中的特定情况下,很容易不可变视图返回到使用return Collections.unmodifiableList(list);

当然类,如果有可能的是,后备数据仍然会被修改,那么你需要做一个完整的副本。

0

首先,setter和getters是一个坏OO的指示。一般来说,面向对象的概念是你要求对象为你做点什么。设置和获取是相反的。 Sun应该已经想出了一些实现Java bean的其他方式,这样人们就不会选择这种模式并认为它是“正确的”。其次,你拥有的每个对象本身都应该是一个世界 - 一般来说,如果你打算使用setter和getter,它们应该返回相当安全的独立对象。这些对象可能是也可能不是不可变的,因为它们只是一流的对象。另一种可能性是他们返回始终不变的本地类型。所以说“setter和getters应该返回一些不变的东西”没有太大意义。对于自己制作不可变对象,除非你有很强的理由不要(最终应该是默认值,“可变”应该是一个覆盖默认值的关键字) 。这意味着只要有可能,对象就是不变的。对于可能传递的预定义准对象事物,我建议您使用它们自己的方法将诸如集合和值组的组合包装到自己的类中。实际上,我几乎从不绕过一个不受保护的集合,因为在使用精心设计的对象应该是显而易见的时候,您没有给出任何指导/帮助。安全也是一个因素,因为允许某人访问课堂内的集合,这实际上不可能确保课程始终有效。

+0

“制定者和获取者是糟糕的OO的表现” - 坦率地说,这确实是完全不真实的。 – Calanus 2009-04-20 14:11:43

+0

虽然它经常被辩论,但它并不明显是不真实的,当你开始真正理解面向对象时,你意识到它实际上是一个问题的良好指示器(就是过度使用)。在OO中,你要求一个对象用它自己的数据做一些事情,因此需要从外部访问数据表明访问数据的代码做错了,它应该传递数据并要求该对象对它自己的数据进行操作它是自己的方法。这不是100%,但这是一个有效的目标。 – 2009-04-20 16:21:00

3

Joshua Bloch在他出色的“Effective Java”一书中说过,当返回像这样的东西时,你应该总是做出防御性的副本。这可能有点极端,特别是如果ContactDetails对象不是可复制的,但它总是安全的。如果有人怀疑总是偏爱代码安全而不是性能 - 除非分析表明克隆是一个真正的性能瓶颈。

实际上您可以添加几种保护级别。您可以简单地返回该成员,这实质上是给予班级内部任何其他类访问权限。非常不安全,但公平地广泛完成。如果你想改变内部以便ContactDetails存储在一个Set中,它也会在以后给你带来麻烦。您可以返回一个新创建的列表,并引用内部列表中的相同对象。这更安全 - 另一个类无法删除或添加到列表中,但它可以修改现有对象。第三,返回一个新创建的列表与ContactDetails对象的副本。这是安全的方式,但可能很昂贵。

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