3

This is a general issue/problem that I have come across. I wondered if anyone knows of any well suited design patterns or techniques.

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
}

The class above takes external objects from a queue and constructs MyBuilderClass objects.

A MyBuilderClass object is successfully built if all of the fields have been set to non-null non-empty values.

There will be many MyBuilderClass objects that cannot be built because data will be missing from the ExternalObject.

My problem, what is the best way to detect if an object has been correctly built?

  • I could check for null or empty values in the set methods and throw an exception. The problem with this approach is throwing exceptions is expensive and it will clogg the log files up because there will be many instances where an object cannot be built;

What other approaches could I use?

3
  • have you tried the Builder pattern? either it's correct, or it's not built
    – Stultuske
    Commented Dec 19, 2017 at 10:24
  • @Stultuske im not 100% sure how the builder pattern handles required values that are missing i.e. trying to set a required value to null or empty? Commented Dec 19, 2017 at 10:56
  • that depends on how you implement it. A lot of companies have an assertion that will make the build fail if not all mandatory information is present
    – Stultuske
    Commented Dec 19, 2017 at 10:57

5 Answers 5

0

Correct me if I'm wrong: you are trying to find a good way to check if an object is valid, and if it is not, tell the client code about this without using an exception.

You can try a factory method:

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();
    }
}

Now you know whether an object is valid without using an exception. You just need to check whether the value returned by initWithExternalObject is null.

0

I wouldn't throw exceptions in cases that aren't exceptional. And as the only way for a constructor not to produce an object is to throw, you should not delay validation to the constructor.

I'd still recommend the constructor to throw if its results were to be invalid, but there should be a validation before that, so you don't even call the constructor with an invalid ExternalObject.

It's up to you if you want to implement that as a static method boolean MyBuilderClass.validate(ExternalObject) or by using the builder pattern with this validation.

0

Another approach for such a validation is to use java Annotations:

  1. Make a simple annotaion class, let's say Validate:

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @interface Validate {
    boolean required() default true;
    }
    
  2. then annotate the fields you want to be present as @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. And then add this method in the MyBuilderClass class, in order to check if your Object is built correctly:

    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. Here is an example of use :

     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());
    

    }

Output : true because the object is correctly built.

This will allow you to choose the fields that you want to be in the structure by reflection, without bringing those inherited from a base class.

-1

As this previous answer suggests, here are 2 options either of which should be added after you have tried to set the variables.

use reflection to check whether any of the variables are null. (As mentioned in comments this will check all fields in this object but be careful with fields in any superclasses).

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

perform a null check on each variable.

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

Previous answer

5
  • 1
    I donwvoted because the reflection approach checks for non-null in ALL fields declared in THIS class. What about inherited fields (missing in your approach) or helper fields (checked although null might be OK for them)? Commented Dec 19, 2017 at 10:47
  • 1
    And I think you got the logic wrong. None of the fields should be null, but you're checking for all of them being null. Commented Dec 19, 2017 at 10:52
  • both fair points, let me clarify. However they were just meant to be illustrative of the approaches that could be used Commented Dec 19, 2017 at 11:05
  • Reflection is really inappropriate, and slow as molasses to boot. Commented Dec 19, 2017 at 14:50
  • I don't think that reflection should be dismissed as an option. I appreciate that it is slower than accessing field values directly but these checks will only be performed once on creation so the overhead could well be accepted. The benefits of such an approach over checking each field value using direct access would be that if the builder class were to be extended to have additional fields the developer would not be required to remember to add those fields to the null check. In addition the reflection check could be reused on other builders Commented Dec 19, 2017 at 15:05
-1

From what I've come across you could overwrite the equals method of your object and compare it with a valid example object. Its dirty and might only work in some cases.

Your approach is the best I could think of. Write a seperate method or class that has for example a static validate method. You could reuse it anywhere.

3
  • 1
    so, either the equals method is rubbish, or all the "valid" instances need to have the same values?
    – Stultuske
    Commented Dec 19, 2017 at 10:26
  • 1
    This very much reads like a comment, not like a answer to the question.
    – GhostCat
    Commented Dec 19, 2017 at 10:27
  • I mean I gave the hint with the equals attempt. Worth a thought. And he almost answered his own question with pobably the most valid attempt. Commented Dec 19, 2017 at 10:43

Not the answer you're looking for? Browse other questions tagged or ask your own question.