1

What is the best way in Java to enforce class instance invariants (i.e. to ensure that certain statements are true right before and after calling any public method)?

I will include below an example (the one that has made me wonder about this question), but I am in fact more interested in a general solution for imposing class-instance invariants.

Let's say I have the following classes:

public class Constraint
{
  private int cardinality;

  // constructor and getter/setter omitted
}

public class Edge
{
  private int minCardinality = 1;
  private int maxCardinality = Integer.MAX_VALUE;
  private Constraint constraint = null;

  // constructors, getters/setters and other methods omitted
}

and that I'd like to enforce the following statements on the Edge class:

  • 0 <= minCardinality <= maxCardinality
  • if (constraint != null) then (minCardinality <= constraint.getCardinality() <= maxCardinality)

Here Edge instances are created by a parser with (obligatory) attributes minCardinality and maxCardinality set. The (optional) constraint attribute is left blank. The instances are then handed over to another module (a test frame generator) which might or might not set the constraint via a setter method.

Dušan Rychnovský
  • 11,699
  • 8
  • 41
  • 65

1 Answers1

4

Use a builder and put the checks in it; this way your Edge class does not care:

@Immutable // See JSR 305
public final class Edge
{
    private final int minCardinality;
    // ...

    public static Builder newBuilder() { return new Builder(); }

    // Edge has no public constructor anymore, only this one:
    private Edge(final Builder builder)
    {
        minCardinality = builder.minCardinality;
        // etc
    }

    @NotThreadSafe // See JSR 305
    public static final class Builder
    {
        private int minCardinality;
        // ...

        private Builder() {}

        public Builder withMinCardinality(final int minCardinality)
        {
            this.minCardinality = minCardinality;
            return this;
        }
        // etc

        public Edge build()
        {
            // checks here
            return new Edge(this);
        }
    }
}

Usage:

final Edge edge = Edge.newBuilder().withMinCardinality(xxx).etc().etc().build();
fge
  • 119,121
  • 33
  • 254
  • 329
  • Thank you. This is an interesting solution but I'm afraid that it would not quite fit for my situation. The thing is that the Edge object is not immutable. It is instantiated with just the minCardinality and maxCardinality attributes, while the constraint attribute is set later. I would like to make sure that at any given time (i.e. both after instantiation and after the constraint has been set), all statements hold. – Dušan Rychnovský Jun 14 '13 at 09:03
  • Well, can't you just pass the builder around instead of the edge and only `.build()` when you actually need it? (modifying this code for this is trivial) – fge Jun 14 '13 at 09:04
  • The part of the application which is responsible for setting the constraint is doing so with regards to actual cardinalities of individual edges. In other words, in order to find out what constraint to set on an edge it first needs find out it's cardinalities. Do you think it would be appropriate to add methods to obtain the cardinalities to the builder? – Dušan Rychnovský Jun 14 '13 at 09:10
  • Well yes, you can always do that. That deviates from the pattern a little, but if you need it, go for it ;) – fge Jun 14 '13 at 09:11
  • In this particular example, wouldn't an assertion in the constructor of the Edge class and in the constraint setter method do just fine as well? And would you propose a builder pattern whenever there is a need to impose invariants on a class? – Dušan Rychnovský Jun 14 '13 at 09:14
  • 1
    It is of course possible to do such a check in the setter, the thing is, the class itself may not be in a state where it is "ready" to check the constraint (for instance, you set the `Constraint` member but have not checked the min/max cardinalities). Also, I like immutable classes... So, would I propose a builder every time? Yes. Is this the one-size-fits-all solution? No ;) – fge Jun 14 '13 at 09:21
  • Builders add a lot of boilerplate code in simple class. This is a practice that does not show the right answer and in fact, avoid how to teach the check of invariance. The code must call checkInvariance() method to check the constructor parameters match the requirement for rep invariance. If you don't check, you can pass null or invalid parameters on the Builder that you will fail as well. – Gustavo Rodrigues Feb 27 '19 at 06:21