Search code examples
operator-precedencespecificationsspecification-pattern

Specification Pattern and Boolean Operator Precedence


In our project, we have implemented the Specification Pattern with boolean operators (see DDD p 274), like so:

public abstract class Rule {

    public Rule and(Rule rule) {
        return new AndRule(this, rule);
    }

    public Rule or(Rule rule) {
        return new OrRule(this, rule);
    }

    public Rule not() {
        return new NotRule(this);
    }


    public abstract boolean isSatisfied(T obj);
}


class AndRule extends Rule {

    private Rule one;
    private Rule two;

    AndRule(Rule one, Rule two) {
        this.one = one;
        this.two = two;
    }

    public boolean isSatisfied(T obj) {
        return  one.isSatisfied(obj) && two.isSatisfied(obj);
    }
}

class OrRule extends Rule {

    private Rule one;
    private Rule two;

    OrRule(Rule one, Rule two) {
        this.one = one;
        this.two = two;
    }

    public boolean isSatisfied(T obj) {
        return one.isSatisfied(obj) || two.isSatisfied(obj);
    }
}

class NotRule extends Rule {

    private Rule rule;

    NotRule(Rule obj) {
        this.rule = obj;
    }

    public boolean isSatisfied(T obj) {
        return !rule.isSatisfied(obj);                
    }
}

Which permits a nice expressiveness of the rules using method-chaining, but it doesn't support the standard operator precedence rules of which can lead to subtle errors.

The following rules are not equivalent:

Rule<Car> isNiceCar  = isRed.and(isConvertible).or(isFerrari);
Rule<Car> isNiceCar2 = isFerrari.or(isRed).and(isConvertible);

The rule isNiceCar2 is not satisfied if the car is not a convertible, which can be confusing since if they were booleans

isRed && isConvertible || isFerrari
would be equivalent to
isFerrari || isRed && isConvertible

I realize that they would be equivalent if we rewrote isNiceCar2 to be isFerrari.or(isRed.and(isConvertible)), but both are syntactically correct.

The best solution we can come up with, is to outlaw the method-chaining, and use constructors instead:

OR(isFerrari, AND(isConvertible, isRed))

Does anyone have a better suggestion?


Solution

  • Your "constructor" solution sounds about right (semantically at least), in that it forces building something closer to an expression tree than a chain. Another solution would be to pull the evaluation functionality out of the Rule implementations so precedence can be enforced (by walking the chain).