Java Beans作为数据存储类是不好的设计吗?

10

通常情况下,JavaPractices.com是一个有好想法的好网站,但这篇文章却让我感到困扰:JavaBeans are bad

该文章列举了几个理由,主要是JavaBean一词意味着“Java Bean是一种可重复使用的软件组件,可以在构建器工具中进行可视化操作”,而不是数据存储,违反了某些模式,并且更加复杂。

现在我可以同意最后一个理由,但在我看来,在列表中使用JavaBeans比嵌套映射更有意义。该文章声称,数据库映射框架应该调用构造函数而不是set *方法,并且对象应该是不可变的。然而,在我看来,当试图构建对象时调用set *方法比new MappedObject("column1", "column2", "yet another column", "this is stupid"); 更易于阅读。

除了数据库映射之外,我还使用JavaBean样式的类来处理其他事情,例如针对IRC机器人,每个用户拥有一个对象,该对象会随着各种事物的更新而更新。我不想每次给出新信息时都创建一个新对象,我想将其添加到现有对象中。

因此,我的问题是:使用JavaBeans进行数据存储是否是一种不好的做法,应该避免,还是完全安全?


11
JavaBeans 不仅代表了一种设计模式,更代表了一整个反面模式语言。 - Tom Hawtin - tackline
3
您可以使用构建器来获得不可变性的好处,避免使用超长的构造函数。 - Angelo Genovese
6
考虑学习建造者模式(Builder Pattern)。http://en.wikipedia.org/wiki/Builder_pattern。 - CoolBeans
6个回答

20

看起来你误读了文本。

现在我同意最后一个观点,但在我看来,在列表中使用JavaBeans比嵌套的Map更有意义

文本从未提到嵌套映射作为替代方案。

...应该调用构造函数,而不是set*方法,并且对象应该是不可变的

这是一种好的实践,特别是在处理线程时非常有用。

但我们也不能说使用setter是很糟糕的,特别是当单个线程正在使用对象时。那是完全安全的。

我不想每次给出新信息时都创建一个新对象,我想将其添加到现有对象中。

只要你控制对象,这样做就没有问题,其他人可能更容易只创建一个新对象。

使用JavaBeans作为数据存储是一种不好的做法,应该避免,还是完全安全的?

不,它并不是一个不好的实践。它也不是完全安全的。这取决于情况。

可变对象的问题(不是JavaBeans本身的问题)是使用不同的线程来访问它们。

您必须同步访问以避免一个线程在其他线程访问它时修改该对象。

不可变对象没有这个问题,因为它们不能改变,因此您不必同步任何内容。

要确保对象是不可变的,必须将其属性声明为final。

class MyBean  {
    private final int i;
}

如果你想给MyBean.i赋一个合理的值,你需要在构造函数中指定:

 public MyBean( int i ) {
     this.i = i;
 }

由于变量是final的,您无法使用setter,只能提供getter。

这是完全线程安全的,并且最好的是,您不必同步访问,因为如果两个线程尝试获取 i 的值,它们都将始终看到在实例化时分配的值,您不必同步任何内容。

这既不是坏的实践也不是好的实践。我们大多数人必须使用单个线程,即使是在诸如servlet之类的多线程环境中。

如果将来要处理多线程应用程序,则可以考虑使用不可变的JavaBean ;)

顺便说一下,创建不可变的bean并仍然提供一堆setter的替代方法是使用 Builders

 Employee e = new EmployeeBuilder()
                  .setName("Oscar")
                  .setLastName("Reyes")
                  .setAge(0x1F)
                  .setEmployeeId("123forme")
                  .build(); 

这与常规 bean 中使用的 setXyz 看起来非常相似,但它具有使用不可变数据的好处。

如果您需要更改一个值,可以使用类方法:

 Employee e = Employee.withName( e, "Mr. Oscar");

这个函数会接收一个已有的对象,并将其所有的值复制一份,然后创建一个新的对象……

 public static EmployeeWithName( Employee e , String newName ){
      return new Employee( newName, e.lastName, e.age, e.employeeId );
  }

但是,在单线程模型中,使用Getter/Setter是完全安全的。

顺便说一句,我强烈建议你购买这本书:Effective Java。您将不会后悔,而且您将获得更好地判断类似引用文章的信息。


+1 for good content. 我提到了嵌套映射,因为我在除了数据库行之外的其他事情上使用了bean,比如演化的用户对象。然而,由于两者都在不断演化,我不确定是否采用不可变路线是最好的选择,尽管它确实有一些好处,因为如果我想要更改对象,仍然会遇到同步问题和替换现有对象的困扰。 - TheLQ
@Lord.Quackstar 是的,但大多数情况下单线程已经足够了,而Java Bean也不是什么坏模式。它们只是一种替代方案。 - OscarRyz
你还可以考虑为任何给定的bean添加一个toBuilder方法,这样你就可以将任何bean转换为builder,设置一些字段,然后将对象重建为新对象。因此,你可以执行employee = employee.toBuilder().setName("Mary Poppins").build(); 这将给你一些半可变性。 - Haakon Løtveit

6
我反对使用JavaBeans作为数据存储类的原因是,它们允许存在状态不一致的对象。在这种bean的典型用例中,您需要执行以下步骤:
- 实例化类; - 设置第一个属性; - 设置第二个属性; - ... - 设置最终属性; - 使用对象实例。
现在您的类已经准备就绪。那么问题在哪里呢?在实例化类和设置最终属性之间,您有一个处于内部不一致或无法使用的对象,但是没有任何东西可以防止您意外地使用它。我更喜欢一个系统,在实例化时类会自动处于一致的可用状态。出于这个原因,我更喜欢要么在构造函数中传递所有初始状态,要么如果该初始状态太复杂,则以哈希映射、集合或类似形式传递初始状态。现在的用例场景如下:
- (可选:设置参数对象); - 实例化类; - 使用对象实例。
在这个工作流程中,我没有可能意外地开始使用一个处于不一致状态的对象。如果我使用参数对象,它将不直接用于任何事情,而且它的内容将在我的主类实例化时得到审查。主类本身在从实例化返回后,将给我一个立即可用的对象实例。
当然,这种设置最适合简单的对象。对于更复杂的对象,例如具有可选属性等,则需要更进一步并使用其他人指向您的Builder模式之类的东西。在我看来,当您有更复杂的场景时,建造者是不错的选择,但对于更简单、更直接的参数化,仅使用构造函数参数或某些类型的参数对象就足够了。

3

"JavaBeans模式有严重的缺点。" —— Joshua Bloch,《Effective Java》

我喜欢这些人。将任意引用断章取义已经足以让我不信任这样的文章。

顺便说一下,所提到的书(《Effective Java》)对这两种模式、它们的优缺点和替代方案进行了广泛的讨论。你可能想去看看。但 JavaBeans 没有本质上的问题,只是有时它们不是最佳选择。

编辑:请查看《Effective Java》中的第2条("Consider a builder when faced with many constructor parameters"),位于Google Books上: http://books.google.com/books?id=ka2VUBqHiWkC&lpg=PP1&pg=PA11#v=onepage&q&f=false


我认为该文章的论点非常薄弱,基于断章取义的引用,并且该文章没有提出明显更好的替代方案。换句话说,@Lord.Quackstar:不要轻信互联网上随意一篇文章中所阐述的任何内容。 - Jesper

2
避免使用setter的主要原因是为了不可变性。提前编写不可变代码,可以避免关于这些对象的任何线程问题。
如果你最终得到一个构造函数,它读取
new Person("param 1","param 2","param 3","param 4","param 5","param 6","param 7","param 8")
那么你的对象太复杂了。你需要参数对象(请参见Martin Fowler的《重构》一书)。
从一开始就进行防御性编码,来维护你的代码的人会感谢你(好的),或者诅咒你(因为他们不能懒惰地改变对象)。
当你需要更改对象时,请添加一个拷贝构造函数(即克隆方法)。现代JVM可以轻松快速地处理这个问题,并且几乎没有速度惩罚。你还可以让Hotspot和GC更容易处理此类问题。

0

这是完全安全的,比无休止地嵌套java.util.Map要好得多。您可以获得类型安全性、更易于理解的代码,并避免您的代码出现在 The Daily WTF 上。请注意,实现应该被分离 - 不要将太多逻辑(超出简单验证)混合到您的数据存储类中。您应该阅读有关POJOs的内容。


0

这篇文章的意图是好的,但在实践中避免使用 beans 和 setters 很难坚持。例如,许多情况下不可能让框架使用构造函数,因为名称通常是识别特征,而方法/构造函数参数名称在 .class 文件中不会被维护。(虽然至少有一个 可以解决这个问题。)

Setters 是次佳选择 - 不如“纯”面向对象,但比构造函数更方便,在实践中工作得很好。

当测试因为“糟糕”的 Java beans 而失败时,我会重新考虑是否有问题。但到目前为止我还没有看到过这种情况。唯一我会说它们可能不合适的时候是在多线程代码中有共享的 beans,因为同步共享的可变状态很棘手。避开这个问题,beans 就没问题了。


你认为什么是“糟糕的Java Bean”? - TheLQ
“bad” 是我对文章标题的幽默,我不认为它们本质上是坏的,但像所有模式一样,它们可能被滥用成为反模式。也许在某些情况下,Bean 可能会产生不必要的行为和数据解耦,或者习惯性的单行 getter/setter 最好作为更丰富的计算来完成。如果只有属性是一流的概念,那么我们就可以在不需要字符串(属性名称)和反射的情况下构建模式... - mdma
在实例化类和设置最终属性之间,您拥有一个处于内部不一致或无法使用状态的对象,这就是问题的要点。 - Thufir

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接