Java Bean Class的缺点是什么?

时间:2015-02-08 12:52:03

标签: java

我从 Effective Java

一书中读到了下面提到的这两个陈述

第一

  

不幸的是,JavaBeans模式有其严重的缺点   拥有。因为构造是跨多个调用分开的,所以是一个JavaBean   可能在其建设的中途处于不一致的状态   class没有选择仅通过强制执行一致性   检查构造函数参数的有效性。试图使用   当一个对象处于不一致状态时可能会导致失败   远离包含bug的代码,因此很难   调试。

第二

  

相关的缺点是JavaBeans模式排除了   使类不可变的可能性(第15项),并要求添加   程序员努力确保线程安全。它是   可以通过手动“冻结”来减少这些缺点   对象在构造完成且不允许时   使用直到冷冻,但这种变体很笨重,很少使用   实践。而且,它可能会在运行时导致错误,如编译器   无法确保程序员在对象上调用freeze方法   在使用之前。

,我无法理解这两个陈述究竟要传达的内容,你们能不能帮助我理解上述陈述。

更新

我在这篇文章中已经阅读了答案(不是全部),大多数社区成员建议我使用Constructor Pattern,但在同一本书中这些行已经说过了

  

静态工厂和构造函数共享一个限制:它们没有   可以很好地扩展到大量可选参数。考虑这个案子   表示营养成分标签的类别   包装食品。这些标签有一些必填字段 - 服务大小,   每个容器的份量,每份的卡路里 - 和超过20份   可选字段 - 总脂肪,饱和脂肪,反式脂肪,胆固醇,   钠等。大多数产品只有少数非零值   这些可选字段。

对于此场景,我们使用telescoping constructor模式,但

  

伸缩式构造函数模式有效,但很难写   当有很多参数时,客户端代码仍然难以读取   读者会想知道所有这些价值观的意义和必要性   仔细计算参数,找出答案。长序列相同   键入的参数可能会导致细微的错误。如果客户不小心   反转两个这样的参数,编译器不会抱怨,但是   程序会在运行时行为不端

这就是为什么要使用JavaBeans而不是constructor pattern

4 个答案:

答案 0 :(得分:14)

让我们看看最简单的Java Bean:

class Person {
   private String firstName;
   private String lastName;
   public String getFirstName() {return firstName;}
   public void setFirstName(String firstName) {this.firstName = firstName;}
   public String getLastName() {return lastName;}
   public void setLastName(String lastName) {this.lastName = lastName;}
}

以下代码创建Person的实例并启动它:

Person president = new Person();
p.setFirstName("George");
p.setLastName("Bush");

如你所见:

  1. 初始化确实分为3行。这意味着当完成所有3行并且在此之前处于不一致状态时,对象处于恒定状态。
    1. 该对象确实是可变的:它调用的值可以通过调用setter来改变。
  2. 为什么这么糟糕?因为我们的类Person不是线程安全的,因此我们不能在多线程环境中直接使用它而不考虑同步化。

    这是一个例子。几年前,巴拉克奥巴马成为美国总统。我们如何在代码中表达这一点?

    p.setFirstName("Barak");
    p.setLastName("Obama");
    

    在多线程envronent中president对象处于错误状态时setFristName()已经完成且setLastName()尚未调用,因为该对象包含" Barak Bush&#34 ;这显然是错的。

    解决方案是什么?让我们做一个“不可变的人”

    class Person {
       private final String firstName;
       private final String lastName;
       Person(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
       }
    
       public String getFirstName() {return firstName;}
       public String getLastName() {return lastName;}
    }
    

    正如您所看到的,无法更改存储在对象中的名字或姓氏。字段为final且没有设置者。所以,我们的例子如下:

    人总统=新人(" George"," Bush"); //选举..... 总统=新人("巴拉克","奥巴马");

    由于Person是不可变的,我们无法重新使用Person的旧实例并更改其属性。我们必须改为创建新实例。如果presidentvolatitle,则引用赋值是原子的,因此代码是线程安全的。

    更新

    构造函数的问题在于它们不灵活。我们的例子只有2个参数。但是当课程Person可能有20个或更多字段时,请考虑现实世界。在这种情况下,创建这样的对象非常冗长。

    此外,某些字段可以是可选的。在这种情况下,您可能希望创建具有不同数量参数的多个重载构造函数。为了避免重复的代理代码,通常的技术是使用所谓的伸缩构造函数,即构造函数调用其他构造函数时的模式。这种模式很好,但有些过于冗长,难以修改。

    这只是意味着没有理想的解决方案。每种解决方案都有自己的优点和缺点。

    BTW结合了不可变对象的优点和对象创建和初始化的灵活性的解决方案是构建器模式。

    我不会写出更好地理解这些问题所需的所有示例。但我希望我的回答对你有所帮助。现在您有了一个起点,可以使用其他资源来学习这个问题。祝你好运。

答案 1 :(得分:2)

假设我们有一个Person bean,它有firstnamelastname个属性:

class Person {
    private String firstname;
    private String lastname;
    public String getFirstname() { return firstname; }
    public void setFirstname(String firstname) { this.firstname = firstname; }
    public String getLastname() { return lastname; }
    public void setLastname(String lastname) { this.lastname = lastname; }
}

要完全初始化该类,我们需要设置名字和姓氏。但在这个实现中,没有什么能阻止我们做类似的事情:

Person paulSmith = new Person();
paul.setFirstname("Paul");
paul.getLastname(); // will not throw an error, but is functionnaly invalid
paul.setLastname("Smith");

相比之下,如果您将此类实现为:

class Person {
    private final String firstname;
    private final String lastname;
    public Person(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }
    public String getFirstname() { return firstname; }
    public String getLastname() { return lastname; }
}

您确保在施工后立即完全初始化该课程:

Person paulSmith = new Person("Paul", "Smith");
// impossible to call getLastname() before lastname is initialized

我也让这个班同时成为不可变的:保罗永远不会改变他的名字。根据用例,这可能会或可能不会成为可能,但是不可变类保证是线程安全的,这总是一件好事。

请注意,我可以(也应该)使用我的构造函数来验证始终在有效状态下创建Person

class Person {
    private final String firstname;
    private final String lastname;
    public Person(String firstname, String lastname) {
        if (firstname == null) throw new IllegalArgumentException("a person must have a firstname");
        if (lastname == null) throw new IllegalArgumentException("a person must have a lastname");
        this.firstname = firstname;
        this.lastname = lastname;
    }
    public String getFirstname() { return firstname; }
    public String getLastname() { return lastname; }
}

关于不变性的补充说明:

不可变对象保证是线程安全的(总是很好),并且在你不期望它时没有机会改变。这使得更容易推理不可变对象。不那么惊喜。

话虽这么说,试图推动周围的不可变对象往往会促使你在不同的类中分割数据和操作。很容易拥有对它们进行操作的不可变对象和“服务”。但是如果你这样做,你实际上打破了面向对象设计的第一个原则:“将数据和函数封装在一个组件中”。设计你的课程是不可改变的是一个好主意,你只需要确保你知道在哪里停止。

答案 2 :(得分:0)

java beans = default constructor + getters + setters

现在,假设您要为一个人建模,在您的模型中,每个人都必须有姓名。在java bean约定中,您必须1)创建一个人,然后2)用名称和姓氏填充它。但是在1)和2)之间你有现有的状态不一致的对象,它是一个没有名字的人。在这个琐碎的例子中,它看起来有些夸张,但如果你有一个复杂的系统,它就开始变得重要了。

关于不可变性:它是创建可读和可维护软件的最佳方法之一。越少的状态和可变性越好。如果你的对象必须有setter,那么显然它不是一成不变的。它再次开始在大型复杂系统中起作用,而不是一个简单的hello world样本

答案 3 :(得分:0)

我将通过提醒每个人Java" beans"来开始这篇文章。有点"不同"来自普通的POJO课程。对于例如它们通常使用反射创建,需要公共的无参数构造函数,具有哑的getter / setter等等。

关于第一部分,让我们假设您有一个Employee Java bean,其中包含nameagegender等属性。您还有以下代码位:

  1. 创建bean对象
  2. 根据传入的信息设置一些属性
  3. 我们假设在创建Employee时没有从UI传递name(假设UI组件和bean之间存在绑定)。将使用null调用bean setter,当实际使用 Employee bean而不是实际上" set&#34时,将在代码中稍后捕获;

    关于第二部分,在编写多线程代码时,通常必须非常小心地使用Java bean和可变对象。如果你"分享"跨线程的对象并不断修改字段而不是创建" new"那些,你一定会遇到有“坏”的对象。状态。

    鉴于其他线程在大多数时间内总能看到正确状态的可能性,这些错误很难追踪!