【问题标题】:Checking an object is correctly built in Java检查对象是否在 Java 中正确构建
【发布时间】:2017-12-19 10:21:27
【问题描述】:

这是我遇到的一般问题/问题。我想知道是否有人知道任何非常适合的设计模式或技术。

private ExternalObject personObject; 
private String name;
private int age;
private String address;
private String postCode;

public MyBuilderClass(ExternalObject obj)
     this.personObject=obj;
     build();
}

public build() {
    setName(personObject.getName());
    setAge(personObject.getAge());
    setAddress(personObject.getAddress());
    setPostCode(personObject.getPostCode());
    .
    .
    . many more setters
}

上面的类从队列中获取外部对象并构造 MyBuilderClass 对象。

如果所有字段都设置为非空非空值,则成功构建 MyBuilderClass 对象。

将有许多 MyBuilderClass 对象无法构建,因为 ExternalObject 中将缺少数据。

我的问题,检测对象是否正确构建的最佳方法是什么?

  • 我可以在 set 方法中检查 null 或空值并抛出异常。这种方法的问题是抛出异常代价高昂,并且会阻塞日志文件,因为会有很多实例无法构建对象;

我还可以使用哪些其他方法?

【问题讨论】:

  • 你尝试过Builder模式吗?要么是正确的,要么没有建成
  • @Stultuske 我不是 100% 确定构建器模式如何处理缺少的所需值,即尝试将所需值设置为 null 或空?
  • 这取决于你如何实现它。很多公司都断言,如果不是所有强制性信息都存在,那么构建就会失败

标签: java design-patterns object-oriented-analysis


【解决方案1】:

如果我错了,请纠正我:您正试图找到一种检查对象是否有效的好方法,如果不是,请在不使用异常的情况下告诉客户端代码。

你可以试试工厂方法:

private MyBuilderClass(ExternalObject obj)
     this.personObject=obj;
     build();
}

public static MyBuilderClass initWithExternalObject(ExternalObject obj) {
    // check obj's properties...
    if (obj.getSomeProperty() == null && ...) {
        //  invalid external object, so return null
        return null;
    } else {
        // valid
        MyBuilderClass builder = new MyBuilderClass(obj);
        return builder.build();
    }
}

现在您无需使用异常即可知道对象是否有效。只需检查initWithExternalObject返回的值是否为null即可。

【讨论】:

    【解决方案2】:

    我不会在非异常情况下抛出异常。由于构造函数不产生对象的唯一方法是抛出,因此您不应延迟对构造函数的验证。

    如果结果无效,我仍然建议构造函数抛出,但在此之前应该有一个验证,所以你甚至不要用无效的ExternalObject 调用构造函数。

    您是否希望将其实现为静态方法 boolean MyBuilderClass.validate(ExternalObject) 或通过使用带有此验证的构建器模式来实现。

    【讨论】:

      【解决方案3】:

      这种验证的另一种方法是使用 java Annotations:

      1. 做一个简单的注解类,比如说Validate:

        @Target({ElementType.FIELD})
        @Retention(RetentionPolicy.RUNTIME)
        @interface Validate {
        boolean required() default true;
        }
        
      2. 然后将要显示的字段注释为@Validate(required=true):

        class MyBuilderClass {
        private ExternalObject externalObject;
        
        @Validate(required=true)
        private String name;
        
        @Validate(required=false) /*since it's a primitive field*/
        private int age;
        
        @Validate(required=true)
        private String address;
        
        @Validate(required=true)
        private String postCode;
        
        
        MyBuilderClass(ExternalObject externalObject) {
            this.externalObject = externalObject;
            build();
        }
        
        public void build() {
            setName(personObject.getName());
            setAge(personObject.getAge());
            setAddress(personObject.getAddress());
            setPostCode(personObject.getPostCode());
        }
        //.
        //.
        //. many more setters
        
         }
        
      3. 然后在MyBuilderClass类中添加这个方法,以检查你的Object是否构建正确:

        public boolean isCorrectlyBuilt() throws IllegalAccessException {
           boolean retVal = true;
          for (Field f : getClass().getDeclaredFields()) {
            f.setAccessible(true);
            boolean isToBeChecked = f.isAnnotationPresent(Validate.class);
            if (isToBeChecked) {
                Validate validate = f.getAnnotation(Validate.class);
                if (validate.required()/*==true*/) {
                    if (f.get(this) == null) {
                        retVal = false;
                        break;
                        /* return false; */
                    }
                }
            }
        }
        return retVal;
        }
        
      4. 这是一个使用示例:

         public static void main(String[] args) throws Exception {
          ExternalObject personObject = new ExternalObject();
          personObject.setAge(20);
          personObject.setName("Musta");
          personObject.setAddress("Home");
          personObject.setPostCode("123445678");
        
          MyBuilderClass myBuilderClass = new MyBuilderClass(personObject);
          System.out.println(myBuilderClass.isCorrectlyBuilt());
        

        }

      输出:true,因为对象已正确构建。

      这将允许您通过反射选择要在结构中包含的字段,而无需携带从基类继承的字段。

      【讨论】:

        【解决方案4】:

        正如前面的回答所暗示的,这里有 2 个选项,您应该在尝试设置变量后添加其中任何一个。

        使用反射检查是否有任何变量为空。 (如 cmets 中所述,这将检查此对象中的所有字段,但要小心任何超类中的字段)。

        public boolean checkNull() throws IllegalAccessException {
            for (Field f : getClass().getDeclaredFields())
                if (f.get(this) != null)
                    return false;
            return true;            
        }
        

        对每个变量执行空值检查。

            boolean isValidObject = !Stream.of(name, age, ...).anyMatch(Objects::isNull);
        

        Previous answer

        【讨论】:

        • 我投了反对票,因为反射方法检查在这个类中声明的所有字段中的非空值。继承的字段(您的方法中缺少)或辅助字段(尽管 null 可能对它们没问题,但已检查)呢?
        • 我认为你的逻辑是错误的。所有字段都不应为空,但您正在检查所有字段是否为空。
        • 两个公平点,让我澄清一下。然而,它们只是为了说明可以使用的方法
        • 反射真的很不合适,而且启动起来像糖蜜一样慢。
        • 我不认为反射应该被视为一种选择。我很欣赏它比直接访问字段值要慢,但这些检查只会在创建时执行一次,因此可以接受开销。与使用直接访问检查每个字段值相比,这种方法的好处是,如果要扩展构建器类以具有其他字段,则不需要开发人员记住将这些字段添加到空值检查中。此外,反射检查可以在其他构建器上重用
        【解决方案5】:

        根据我遇到的情况,您可以覆盖对象的 equals 方法并将其与有效的示例对象进行比较。它很脏,可能只在某些情况下有效。

        您的方法是我能想到的最好的方法。编写一个单独的方法或类,例如一个静态验证方法。你可以在任何地方重复使用它。

        【讨论】:

        • 那么,equals 方法是垃圾,还是所有“有效”实例都需要具有相同的值?
        • 这读起来很像评论,而不是问题的答案。
        • 我的意思是我用 equals 尝试给出了提示。值得思考。他几乎用最有效的尝试回答了自己的问题。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-30
        • 2012-06-01
        • 2017-08-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多