OO Design: Liskov Substitution Principle 4/13/2015 OO Design: Liskov Substitution 1 Liskov Substitution Principle But we must be careful when we implement subclass to ensure that we don’t violate the LSP public class Rectangle { private double width, height; Rectangle(double w, double h) { width = w; height = h; } public double area() { return width * height; } public void setWidth(double w) { width = w;} public void setHeight(double h){ height = h;} public double getWidth() { return width; } public double getHeight() { return height;} } 4/13/2015 OO Design: Liskov Substitution 2 LSP Example public class Square extends Rectangle { Square(double s) { super(s,s); } public void setWidth(double w) { super.setWidth(w); super.setHeight(w);} public void setHeight(double h){ super.setWidth(h); super.setHeight(h);} } 4/13/2015 OO Design: Liskov Substitution 3 LSP Example public class TestRectangle { public static void testLSP(Rectangle r) { r.setWidth(4.0); r.setHeight(5.0); System.out.println(r.area()); assert(r.area()= 20); } } 4/13/2015 OO Design: Liskov Substitution 4 What Went Wrong? Consider the postcondition for setWidth in classes Rectangle and Square: Rectangle: width == w && height == old.height Square: width == w && height == w The postcondition for Square is weaker than the postcondition for Rectangle because it does not attempt to enforce the clause (height == old.height) 4/13/2015 OO Design: Liskov Substitution 5 Rule When you override a method in a base class, the precondition of the overriding method should be weaker than the precondition of the overriden method: • The more useful a procedure is for clients • The more difficult it is to implement correctly “Weaker” means the derived class should consider more inputs to the overriding method 4/13/2015 OO Design: Liskov Substitution 6 Rule When you override a method in a base class, the postcondition of the overriding method should be stronger than the postcondition of the overriden method: “Stronger” means the derived class should produce less outputs from the overriding method 4/13/2015 OO Design: Liskov Substitution 7 So, void testRectangleSetWidth(Rectangle rec) { double oldHeight = rec.getHeight(); rec.setWidth(5); // postconditions of Rectangle assertTrue (5 == rec.getWidth() && oldHeight == rec.getHeight()); } 4/13/2015 OO Design: Liskov Substitution 8 Subtype Substitution If B is a subtype of A, everywhere the code expects an A, a B can be used instead Examples: c1 must be a subtype of Cell Cell c = c1; (note A is a subtype of A) Cell c = new ConwayLifeCell (); ConwayLifeCell c = new Cell (); 4/13/2015 OO Design: Liskov Substitution 9 Inheritance To implement a subtype, it is often useful to use the implementation of its supertype This is also called “subclassing” class B extends A B is a subtype of A B inherits from A class C implements F C is a subtype of F both subtyping and inheritance just subtyping No way to get inheritance without subtyping in Java 4/13/2015 OO Design: Liskov Substitution 10 How do we know if saying B is a subtype of A is safe? Substitution Principle: If B is a subtype of A, everywhere the code expects an A, a B can be used instead and the program still satisfies its specification 4/13/2015 OO Design: Liskov Substitution 11 Subtype Condition 1: Signature Rule We can use a subtype method where a supertype methods is expected: • • • • Subtype must implement all of the supertype methods Argument types must not be more restrictive Result type must be at least as restrictive Subtype method must not throw exceptions that are not subtypes of exceptions thrown by supertype 4/13/2015 OO Design: Liskov Substitution 12 Signature Rule class A { public RA m (PA p) ; } class B extends A { public RB m (PB p) ; } RB must be a subtype of RA: RB <= RA PB must be a supertype of PA: PB >= PA 4/13/2015 OO Design: Liskov Substitution 13 Substitution Mystery … (in client code) MysteryType1 mt1; MysteryType2 mt2; MysteryType3 mt3; … (anything could be here) mt1 = mt2.m (mt3); If the Java compiler accepts this code, which of these are guaranteed to be true: a. The static type of mt2 is MysteryType2 b. At the last statement, the run-time type of mt2 is MysteryType2 c. MysteryType2 has a method named m d. The MysteryType2.m method takes a parameter of type MysteryType3 e. The MysteryType2.m method returns a subtype of MysteryType1 f. After the last statement, the run-time type of mt1 is MysteryType1 4/13/2015 OO Design: Liskov Substitution 14 … (in client code) MysteryType1 mt1; MysteryType2 mt2; MysteryType3 mt3; … (anything could be here) mt1 = mt2.m (mt3); a. The static type of mt2 is MysteryType2 TRUE: the static type is obvious from the declaration. b. At the last statement, the run-time type of mt2 is MysteryType2 FALSE: we only know the run-time type <= MysteryType2 c. MysteryType2 has a method named m TRUE d. The MysteryType2.m method takes a parameter of type MysteryType3 FALSE: we only know it takes a parameter >= MysteryType3 e. The MysteryType2.m method returns a subtype of MysteryType1 TRUE: the assignment type checking depends on this f. After the last statement, the run-time type of mt1 is MysteryType1 FALSE: we only know that the run-time type <= MysteryType1 4/13/2015 OO Design: Liskov Substitution 15