2

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)?

munHunger
  • 2,572
  • 5
  • 34
  • 63
  • You can assign a default value to your "value" variable in the default constructor. – zappee Mar 28 '18 at 09:34
  • @zappee yeah, but as I wrote just under the code, it doesn't always make sense to have a default value. for example if it is a foreign ID, it must match some other objects ID, and it seems really bad to create another object to have as a default – munHunger Mar 28 '18 at 09:39
  • 1
    @munHunger if you need to "parse" your object then it is more a DTO than a business object, meaning it should not implement any logic. Make it a dumb object and validate when parsing into your real business object. – gpeche Mar 28 '18 at 09:39
  • Most such framework do not impose to have a public default ctor. Java external serialization for example necessitate a default ctor, but a private one is sufficient. Reflection can access private members... – Jean-Baptiste Yunès Mar 28 '18 at 09:44
  • @gpeche so you are saying abstract that away just to keep the precondition? I think that I agree with this statement, but where is the precondition in this case? on the business object that wrapps the DTO? – munHunger Mar 28 '18 at 09:45
  • 1
    @munHunger simplifying a lot, I would transform my code so that somewhere you do something like `businessModel = parse(modelDTO)`. Then you put the validation either in the `parse()` logic or in the `businessModel` constructor. As `businessModel` is ideally a POJO, it is isolated from framework restrictions. – gpeche Mar 28 '18 at 09:49

1 Answers1

3

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...
}
DodgyCodeException
  • 5,963
  • 3
  • 21
  • 42