If I use a variable of type Superclass properly in my code..
..it will work right no matter what Subclass object it actually holds.
@BeforeEach
public void setup() {
this.bird = new Eagle();
}
@Test
public void testBirdFlies() {
// THIS TEST SHOULD PASS
// REGARDLESS OF THE ACTUAL TYPE
assertTrue(this.bird.fly());
}
Bird.fly()
Eagle.fly()
bird.fly()
eagle.fly()
Tests use the
Supertype properly!
wider
narrower
narrower
wider
Preconditions
Postconditions
narrower
wider
Preconditions
Postconditions
Reject nothing that "used to be" allowed!
Produce nothing that "used to be" forbidden!
Barbara Liskov
Strong behavioral subtyping:
a semantic rather than syntactic relation
Superclass
Subclass
// REQUIRES: input >= 0
// EFFECTS: sets the input
public void setInput(int input) {
this.input = input;
}
// REQUIRES: input >= 100
// EFFECTS: sets the input
@Override
public void setInput(int input) {
super.setInput(input);
}
Y
N
Y
Is precondition narrower?
Is postcondition wider?
Is LSP violated?
A
B
C
Superclass
Subclass
// REQUIRES: input >= 0
// EFFECTS: sets the input
public void setInput(int input) {
this.input = input;
}
// REQUIRES: input >= -100
// EFFECTS: sets the input
@Override
public void setInput(int input) {
super.setInput(input);
}
N
N
N
Is precondition narrower?
Is postcondition wider?
Is LSP violated?
A
B
C
Superclass
Subclass
// EFFECTS: returns an integer
// in the range [0, 100)
public int produceValue() {
return input % 100;
}
// EFFECTS: returns an integer
// in the range [0, 200)
@Override
public int produceValue() {
return input % 200;
}
Y
N
Is precondition narrower?
Is postcondition wider?
Is LSP violated?
A
B
C
Y
Superclass
Subclass
// EFFECTS: returns an integer
// in the range [0, 100)
public int produceValue() {
return input % 100;
}
// EFFECTS: returns an integer
// in the range [0, 50)
@Override
public int produceValue() {
return input % 50;
}
N
N
Is precondition narrower?
Is postcondition wider?
Is LSP violated?
A
B
C
N
Superclass
Subclass
// REQUIRES: input is -1, 0, or 1
// EFFECTS: sets the input
public void setInput(int input) {
this.input = input;
}
// REQUIRES: input >= 0
// EFFECTS: sets the input
@Override
public void setInput(int input) {
this.input = input;
}
N
Is precondition narrower?
Is postcondition wider?
Is LSP violated?
A
B
C
Y
"Narrower" really means
"rejects anything that used to be allowed"
Y
public class Square extends Rectangle {
@Override
public int setDimensionsReturnSurfaceArea(int height, int width) {
if (height != width) {
throw new RuntimeException("For a Square implementation height must match width");
}
return super.setDimensionsReturnSurfaceArea(height, width);
}
}
public class Rectangle {
protected int height;
protected int width;
public int setDimensionsReturnSurfaceArea(int height, int width) {
this.height = height;
this.width = width;
return height * width;
}
}
Valid inputs become more limited with subtype!
public class ShapeTest {
private Rectangle rectangle;
@BeforeEach
public void setup() {
this.rectangle = new Square();
}
@Test
public void testSetDimensionsReturnSurfaceArea() {
// THIS WILL THROW AN EXCEPTION!
int surfaceArea = this.rectangle.setDimensionsReturnSurfaceArea(10, 20);
assertEquals(200, surfaceArea);
}
}
Test based on the super type does not pass on the subtype!
public interface DraftDao {
Draft getDraft(long draftId);
// ...
}
@Override
public Draft getDraft(long draftId) {
// Drafts are stored in session by
// pageId (not by their own id)
throw new UnsupportedOperationException(
"the SessionDraftDao does not support Draft getDraft(long id)"
);
}
That's true for all design principles..
..we should understand the tradeoff.
Is precondition narrower?
Is postcondition wider?
Is LSP violated?
A
B
C
Superclass
Subclass
// REQUIRES: a sorted tree
// EFFECTS: returns true if the element is
// in the tree, returns false otherwise
// REQUIRES: any tree
// EFFECTS: returns true if the element is
// in the tree, returns false otherwise
N
N
N
Superclass
Subclass
// REQUIRES: any tree
// EFFECTS: returns true if the element is
// in the tree, returns false otherwise
// REQUIRES: a sorted tree
// EFFECTS: returns true if the element is
// in the tree, returns false otherwise
Is precondition narrower?
Is postcondition wider?
Is LSP violated?
A
B
C
Y
N
Y
Superclass
Subclass
// REQUIRES: a sorted tree
// EFFECTS: returns 1 if the element is
// in the tree, returns 0 otherwise
// REQUIRES: a sorted tree
// EFFECTS: if the element is in the
// tree, returns the number of elements
// in the tree that are larger,
// otherwise returns 0
Is precondition narrower?
Is postcondition wider?
Is LSP violated?
A
B
C
N
Y
Y
class Pump {
public static final int COST_PER_LITRE = 1.45;
// REQUIRES: 10 * COST_PER_LITRE <= amount
// <= 30 * COST_PER_LITRE
// EFFECTS: payment is recorded
void prePay(int amount) {
// stub
}
// EFFECTS: returns at least 10 litres of gas
int dispense() {
return 0; // stub
}
}
class APump extends Pump {
// REQUIRES: 5 * COST_PER_LITRE <= amount
// <= 30 * COST_PER_LITRE
// EFFECTS: payment is recorded
void prePay(int amount) {
// stub
}
// EFFECTS: returns at least 5 litres of gas
int dispense() {
return 0; // stub
}
}
class BPump extends Pump {
// REQUIRES: 20 * COST_PER_LITRE <= amount
// <= 30 * COST_PER_LITRE
// EFFECTS: payment is recorded
void prePay(int amount) {
// stub
}
// EFFECTS: returns at least 20 litres of gas
int dispense() {
return 0; // stub
}
}
Is precondition narrower?
Is postcondition wider?
Is LSP violated?
A
B
C
N-N-N
N-Y-Y
Y-N-Y
N-N-N
No good substitute if LSP is violated!
?-?-?
?-?-?
?-?-?
?-?-?
//REQUIRES: choice is one of "a", "b", "c" or "d"
//EFFECTS: (omitted)
public void handleChoice(String choice) {
// stub
}
Is precondition narrower?
//REQUIRES: choice is one of "A", "B", "C" or "D"
1
//REQUIRES: choice is one of "a", "b", "c", "d", "e"
2
//REQUIRES: choice is one of "b", "c", "d", "e", "f", "g"
3
//REQUIRES: choice is one of "a", "A", "b", "B", "c", "C", "d" or "D"
4
//REQUIRES: choice is one of "a", "b", "c"
5
N
Y
N
Y
Y
//EFFECTS: returns a speed of 1.0, 1.5, 2.0, 2.5 or 3.0
public double computeSpeed() {
return 1.0;// stub
}
Is postcondition wider?
N
N
Y
Y
//EFFECTS: returns a speed of 1.0, 2.0 or 3.0
1
//EFFECTS: returns a speed in the range 1.0 to 1.5 or in the range 2.5 to 3.0
2
//EFFECTS: returns a speed of 0.0, 1.0, 2.0 or 3.0
3
//EFFECTS: returns a speed of 1.5 or 2.5
4