Search code examples
javavalidationconstructorpreconditions

forcing preconditions with empty constructor


I have a couple of objects that I need to be able to parse with, among other things, jersey. This forces me to explicitly add an empty constructor since the frameworks are instanciating using reflection and the empty constructor.

The problem I am seeing with this is that I cannot force any preconditions. Take the following code as an example:

public class Model {
    /**
     * 0 < value < 100
     */
    int value;

    public Model() {} //The much needed empty constructor

    public Model(int value) {
        if(value < 1 || value > 99)
            throw new IllegalArgumentException("Value must be between 1 and 99 inclusive");
        this.value = value;
    }
}

Here the value does have a precondition and depending on usage it might not make sense to set this to any default value(for example if value is an ID that must exist in a database). However since the other frameworks need an empty constructor, it is possible to create a Model object that breaks the precondition and therefore is invalid.

So I am a bit curious as to how this is usually solved. Is there a way to have the empty constructor only open for reflection calls? Or is it more standard to accept that it is wrong and create a isValid function in it that you can call to make sure that the preconditions hold? or perhaps have a different validator object that checks its validity(to keep the model clear from business logic)?


Solution

  • You have a situation whereby a framework forces you to accept the creation of initially invalid objects. Presumably, the framework must then use setters to make the object valid before it's used. You can use a Builder to ensure that only valid objects can be created. Your framework only knows about the Builder, not the Model itself:

    @Resource // or whatever your framework needs
    public class ModelBuilder {
        private int value;
    
        public ModelBuilder() {}
    
        public setValue(int value) {
            if(value < 1 || value > 99)
                throw new IllegalArgumentException("Value must be between 1 and 99 inclusive");
            this.value = value;
        }
    
        public Model build() {
            if (value == 0)
                throw new IllegalStateException("Value must be set before building.");
            return new Model(value);
    }
    
    public class Model {
        /**
         * 0 < value < 100
         */
        private final int value;
    
        public Model(int value) {
            if(value < 1 || value > 99)
                throw new IllegalArgumentException("Value must be between 1 and 99 inclusive");
            this.value = value;
        }
    
        // Other methods...
    }