CPSC 210
D2: Liskov Substitution Principle (LSP)
Learning Goals
- To state the Liskov Substitution Principle (LSP)
- To identify violations of the LSP in existing code
- To ensure the LSP is satisfied when designing type hierarchies
LSP
English Explanation
If I use a variable of type Superclass properly in my code..
..it will work right no matter what Subclass object it actually holds.
LSP - Nutshell Explanation
-
When a subtype is substituted for its super type:
-
the subtype must provide the expected behaviours of the super type
- any correct test based on the super type must pass on the subtype
-
the subtype must provide the expected behaviours of the super type
@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!
LSP - Emotional Explanation (1)
-
Subtype method must NOT:
- narrow the range of inputs that it accepts (specified in requires clause / precondition)
- widen the range of outputs that it produces (specified in effects clause / postcondition)
wider
narrower
narrower
wider
Preconditions
Postconditions
LSP - Emotional Explanation (2)
-
Subtype method must NOT:
- narrow the range of inputs that it accepts (specified in requires clause / precondition)
- widen the range of outputs that it produces (specified in effects clause / postcondition)
narrower
wider
Preconditions
Postconditions
Reject nothing that "used to be" allowed!
Produce nothing that "used to be" forbidden!
LSP - Scientific Explanation
Barbara Liskov
Strong behavioral subtyping:
a semantic rather than syntactic relation
LSP - Example (1)
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
LSP - Example (2)
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
LSP - Example (3)
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
LSP - Example (4)
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
LSP - Example (5)
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
LSP
Another Example
LSP - Another Example (1)
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!
LSP - Another Example (2)
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!
The Real World
Is full of violations!
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.
Lecture Ticket
Lecture Ticket (1)
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
Lecture Ticket (2)
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
Lecture Ticket (3)
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
Lecture Lab (1)
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!
?-?-?
?-?-?
?-?-?
?-?-?
Lecture Lab (2)
//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
Lecture Lab (3)
//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
D2: Liskov Substitution Principle
The End - Thank You!
CPSC210 - D2: Liskov Substitution Principle
By meghanallen
CPSC210 - D2: Liskov Substitution Principle
- 96