0

Suppose I have two unrelated interfaces with the same method:

interface Table {
    /**
     * @param width (0 < width <= 100)
     */
    void setWidth(int width);
}

interface Square {
    /**
     * @param width (50 <= width <= 10000)
     */
    void setWidth(int width);
}

Then I create a class which implements both of these interfaces:

class SquareTable implements Square, Table {
    void setWidth(int width) {
        precondition = ...
        if (!precondition)
            throw new PreconditionFailedException("Precondition failed: ...");
    }
}

How precondition expressions should be combined to satisfy Liskov substitution principle? Should they and'ed or or'ed? I.e. what should be written instead of ...:

width > 0 && width <= 10000 // using OR
width >= 50 && width <= 100 // using AND

And which rule should be used for postconditions?

interface Table {
    /**
     * @return width (0 < width <= 100)
     */
    int getWidth();
}

interface Square {
    /**
     * @return width (50 <= width <= 10000)
     */
    int getWidth();
}

class SquareTable implements Square, Table {
    int getWidth() {
        postcondition = ...
        if (!postcondition)
            throw new PostconditionFailedException("Postcondition failed: ...");
    }
}

width > 0 && width <= 10000 // using OR
width >= 50 && width <= 100 // using AND
2
  • 2
    The Liskov Substitution Principle is about being able to substitute a descendant with its ancestor, ie not negating/disabling the ancestor's behavior in a descendant. Your setWidth problem has nothing to do with that. what you need is something like Delphi's method resolution for interfaces which enables you to have two separate setWidth methods: one for each interface. Helps to keep the logic for both interface implementations separate as well. No idea though how to do that in Java... Commented Sep 5, 2013 at 21:26
  • For a square table, the width is from 0 to 1000. That is your new constraint. It's not pretty because the design needs to be redone to better solve the problem. Commented Sep 6, 2013 at 5:10

3 Answers 3

1

Preconditions

Given that the implementer must be usable as either a Table or a Square, the precondition must be the union (OR) of the two interface preconditions (i.e. width > 0 && width <= 10000).

Postconditions

The implementation must satisfy both interface postconditions, therefore its postcondition could be the intersection (AND) of the two interface preconditions (i.e. width >= 50 && width <= 100).

Implementation

One possible implementation that doesn't violate LSP is to use min/max bounds to keep width >= 50 && width <= 100, e.g. setWidth(10000) effectively performs setWidth(100).

Generally speaking, this isn't a very good solution since it is opaque to setWidth's caller (which might assume that getWidth() would return the value it passed to setWidth). It is much preferable (as Euphoric mentions) to not use one implementation for methods from different interfaces.

2

In this context setWidth and getWidth are different methods for both interfaces, even though they have same name. The reason for this is that pre/post conditions, combined with LSP become part of the method signature. So if the pre/post conditions are different, so are the whole methods.

That is why you should implement them as two different methods. In C#, you can use explicit interface implementation, that allows you to implement different methods for each interface, even though they have same name. Java doesn't have anything like this, so you either need to emulate it somehow or try to live without it.

And just my 2 cents, you should not create pre/post conditions for properties, there should be just condition that applies all the time.

And thinking about it, in this case, the constrainst should be ANDed. Consider this piece of code:

SquareTable st = new SquareTable();
Table t = st;
Square s = st;
t.setWidth(20); // OK, because invariant for Table is 0 < w
int w = s.getWidth(); // returns 20, which breaks the postcondition of Square

In this case, it would be much better if it already failed at t.setWidth(20), according to "fail early" mentality. Thats because it takes more effort debuging from s.getWidth() than from t.setWidth(x).

2
  • I forgot to say that Table, Square and SquareTable are third party classes. I have no control on them. My task is to add preconditions and postconditions and not to break LSP. Commented Sep 6, 2013 at 6:27
  • Java takes the view that if T can be cast to U, and (U)someT.member exists, it should be the same as someT.member. I can see that such a rule simplifies the framework design, but at the expense of expressiveness; in a sense, the real problem is interfaces with insufficiently specific names (e.g. if renderableTable had a members getNumberOfColumns and getGraphicalWidth, there would be no conflict) but in many cases things like getWidth are easier to read than getGraphicalWidth.
    – supercat
    Commented Jan 13, 2014 at 18:02
0

The simplest case is that the precondition should be ORed (if you meet the conditions for either interface, then your method can be called) and the postcondition should be ANDed (the return must satisfy all constraints that apply, no matter which interface you had).

You can loosen the postcondition slightly depending on the preconditions satisfied. You only need to meet the postconditions for the interface that you met the preconditions for. Because if you don't meet the precondition for one interface, then you shouldn't be being called for that interface.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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