【问题标题】:What is the disadvantages of Java Bean Class?Java Bean 类的缺点是什么?
【发布时间】:2015-02-08 12:52:02
【问题描述】:

我从Effective Java

一书中读到了下面提到的这两条语句

第一

不幸的是,JavaBeans 模式有其严重的缺点。 自己的。因为构造是跨多个调用拆分的,所以一个 JavaBean 可能在其构造的中途处于不一致的状态。 类没有强制一致性的选择,仅仅通过 检查构造函数参数的有效性。尝试使用 处于不一致状态的对象可能会导致失败 远离包含错误的代码,因此难以 调试。

第二次

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

,我无法理解这两个语句到底想表达什么,你们能帮我理解上面的语句吗?

更新

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

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

对于这个场景,我们使用telescoping constructor 模式但是

伸缩构造函数模式有效,但很难编写 有很多参数时的客户端代码,而且更难阅读 它。读者不知道所有这些价值观的含义和必须 仔细计算参数以找出答案。相同的长序列 类型化的参数可能会导致细微的错误。如果客户不小心 反转两个这样的参数,编译器不会抱怨,但是 程序在运行时会出现异常

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

【问题讨论】:

    标签: java


    【解决方案1】:

    让我们看看最简单的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 来更改。

    为什么会这样?因为我们的类Person 不是线程安全的,因此我们不能在不考虑同步的情况下直接在多线程环境中使用它。

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

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

    在多线程环境中,president 对象处于错误状态,而 setFristName() 已经完成且 setLastName() 尚未调用,因为该对象包含明显错误的“Barak Bush”。

    解决办法是什么?让我们让 `Person 不可变:

    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 并且没有设置器。因此,我们的示例如下所示:

    个人总裁 = new Person("George", "Bush"); // 选举..... 总统 = 新人(“巴拉克”,“奥巴马”);

    由于Person 是不可变的,我们不能重用Person 的旧instanceo 并更改其属性。我们必须创建新实例。如果 presidentvolatitle 引用分配是原子的,因此代码是线程安全的。

    更新

    构造函数的问题是它们不灵活。我们的示例只有 2 个参数。但是想想现实世界,Person 类可能有 20 个或更多字段。在这种情况下,创建此类对象非常冗长。

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

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

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

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

    【讨论】:

    • 实际上引用分配在 Java 中总是原子的。 volatile 只保证引用不被缓存...
    • @Guillaume,你是对的。我只是不想过多解释。
    • 我明白了你的意思,但你能否对问题的更新部分给予启发,以便我可以将此答案标记为已接受
    • @AlexR 我只是想问一下,如果一个类有多个数据成员,其中一些是强制性的,而另一些则不是,所以此时如果我们使用这样的构造函数new Test(1,2,"Important-Item",null,null)这里null是非强制字段但是为一长串数据成员创建这样的对象很容易让用户混合参数位置,那么在这种情况下使用构造函数好还是不好?
    • @NeerajJain,你明白了。这是这种技术的缺点。但是我在答案末尾提到的Builder 模式有助于解决这个问题。
    【解决方案2】:

    假设我们有一个Person bean,它有一个firstname 和一个lastname 属性:

    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
    

    同时我还使该类不可变:Paul 永远不能更改他的名字。这可能会也可能不会,具体取决于用例,但不可变类保证是线程安全的,这始终是一件好事。

    请注意,我也可以(并且应该)使用我的构造函数来验证 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; }
    }
    

    关于不变性的补充说明:

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

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

    【讨论】:

      【解决方案3】:

      java beans = 默认构造函数 + getter + setter

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

      关于不变性:它是创建可读和可维护软件的最佳方法之一。状态和可变性越少越好。如果您的对象必须有设置器,那么显然它不是一成不变的。再次,它在大型复杂系统中开始很重要,而不是微不足道的 hello world 示例

      【讨论】:

        【解决方案4】:

        我将通过提醒大家 Java“bean”与常规 POJO 类有点“不同”来开始这篇文章。例如它们通常使用反射创建,需要公共的无参数构造函数,具有愚蠢的 getter/setter 等等。

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

        1. 创建 bean 对象
        2. 根据传入的信息设置一些属性

        假设在创建 Employee 时没有从 UI 传递 name(假设 UI 组件和 bean 之间存在绑定)。 bean setter 将使用null 调用,这将在您的代码中稍后在实际使用 Employee bean 时而不是在它实际“设置”时被捕获。

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

        鉴于其他线程大部分时间总是看到正确状态的可能性,这些错误极难追踪!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-09-13
          • 1970-01-01
          • 1970-01-01
          • 2010-12-30
          • 2011-03-04
          • 1970-01-01
          • 2023-03-21
          • 2015-05-25
          相关资源
          最近更新 更多