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

Nutshell Explanation

LSP - Nutshell Explanation

  • When a subtype is substituted for its super type:
    • the subtype must provide the expected behaviours of the super type
       
    • any test based on the super type must pass on the subtype
@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()

LSP

Emotional Explanation

LSP - Emotional Explanation

  • 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)

no change

no change

wider

narrower

narrower

wider

Preconditions

Postconditions

LSP

Scientific Explanation

LSP - Scientific Explanation

  • Principle in object-oriented programming, stating:
    • if S is a subtype of T,
    • then objects of type T may be substituted with objects of type S,
    • without altering any of the desirable properties of the program
  • Strong behavioral subtyping
    • Semantic rather than syntactic relation

LSP - Scientific Explanation (2)

Barbara Liskov

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

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: full of violations

List<String> list = Arrays.asList("One", "Two");
list.remove("One");

https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList-T...-

https://stackoverflow.com/questions/1624144/unsupportedoperationexception-when-trying-to-remove-from-the-list-returned-by-ar

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

D2: Liskov Substitution Principle

The End - Thank You!

CPSC210 - D2: Liskov Substitution Principle

By Felix Grund

CPSC210 - D2: Liskov Substitution Principle

  • 1,049