Liskov Substitution Principle and the Oft Forgot Third Wheel

Posted by Stacy Vicknair on Geeks with Blogs See other posts from Geeks with Blogs or by Stacy Vicknair
Published on Wed, 27 Jun 2012 16:57:05 GMT Indexed on 2012/06/27 21:18 UTC
Read the original article Hit count: 481

Filed under:

Liskov Substitution Principle (LSP) is a principle of object oriented programming that many might be familiar with from the SOLID principles mnemonic from Uncle Bob Martin. The principle highlights the relationship between a type and its subtypes, and, according to Wikipedia, is defined by Barbara Liskov and Jeanette Wing as the following principle:

 

Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

 

Rectangles gonna rectangulate

The iconic example of this principle is illustrated with the relationship between a rectangle and a square. Let’s say we have a class named Rectangle that had a property to set width and a property to set its height.

   1: Public Class Rectangle
   2:     Overridable Property Width As Integer
   3:     Overridable Property Height As Integer
   4: End Class

 

We all at some point here that inheritance mocks an “IS A” relationship, and by gosh we all know square IS A rectangle. So let’s make a square class that inherits from rectangle. However, squares do maintain the same length on every side, so let’s override and add that behavior.

   1: Public Class Square
   2:     Inherits Rectangle
   3:  
   4:     Private _sideLength As Integer
   5:  
   6:     Public Overrides Property Width As Integer
   7:         Get
   8:             Return _sideLength
   9:         End Get
  10:         Set(value As Integer)
  11:             _sideLength = value
  12:         End Set
  13:     End Property
  14:  
  15:     Public Overrides Property Height As Integer
  16:         Get
  17:             Return _sideLength
  18:         End Get
  19:         Set(value As Integer)
  20:             _sideLength = value
  21:         End Set
  22:     End Property
  23: End Class

 

Now, say we had the following test:

   1: Public Sub SetHeight_DoesNotAffectWidth(rectangle As Rectangle)
   2:     'arrange
   3:     Dim expectedWidth = 4
   4:     rectangle.Width = 4
   5:  
   6:     'act
   7:     rectangle.Height = 7
   8:  
   9:     'assert
  10:     Assert.AreEqual(expectedWidth, rectangle.Width)
  11: End Sub

 

If we pass in a rectangle, this test passes just fine. What if we pass in a square?

image

 

This is where we see the violation of Liskov’s Principle! A square might "IS A” to a rectangle, but we have differing expectations on how a rectangle should function than how a square should!

Great expectations

Here’s where we pat ourselves on the back and take a victory lap around the office and tell everyone about how we understand LSP like a boss. And all is good… until we start trying to apply it to our work. If I can’t even change functionality on a simple setter without breaking the expectations on a parent class, what can I do with subtyping? Did Liskov just tell me to never touch subtyping again?

The short answer: NO, SHE DIDN’T. When I first learned LSP, and from those I’ve talked with as well, I overlooked a very important but not appropriately stressed quality of the principle: our expectations. Our inclination is to want a logical catch-all, where we can easily apply this principle and wipe our hands, drop the mic and exit stage left. That’s not the case because in every different programming scenario, our expectations of the parent class or type will be different. We have to set reasonable expectations on the behaviors that we expect out of the parent, then make sure that those expectations are met by the child. Any expectations not explicitly expected of the parent aren’t expected of the child either, and don’t register as a violation of LSP that prevents implementation.

You can see the flexibility mentioned in the Wikipedia article itself:

A typical example that violates LSP is a Square class that derives from a Rectangle class, assuming getter and setter methods exist for both width and height. The Square class always assumes that the width is equal with the height. If a Square object is used in a context where a Rectangle is expected, unexpected behavior may occur because the dimensions of a Square cannot (or rather should not) be modified independently. This problem cannot be easily fixed: if we can modify the setter methods in the Square class so that they preserve the Square invariant (i.e., keep the dimensions equal), then these methods will weaken (violate) the postconditions for the Rectangle setters, which state that dimensions can be modified independently. Violations of LSP, like this one, may or may not be a problem in practice, depending on the postconditions or invariants that are actually expected by the code that uses classes violating LSP. Mutability is a key issue here. If Square and Rectangle had only getter methods (i.e., they were immutable objects), then no violation of LSP could occur.

What this means is that the above situation with a rectangle and a square can be acceptable if we do not have the expectation for width to leave height unaffected, or vice-versa, in our application.

Conclusion – the oft forgot third wheel

Liskov Substitution Principle is meant to act as a guidance and warn us against unexpected behaviors. Objects can be stateful and as a result we can end up with unexpected situations if we don’t code carefully. Specifically when subclassing, make sure that the subclass meets the expectations held to its parent. Don’t let LSP think you cannot deviate from the behaviors of the parent, but understand that LSP is meant to highlight the importance of not only the parent and the child class, but also of the expectations WE set for the parent class and the necessity of meeting those expectations in order to help prevent sticky situations.

 

Code examples, in both VB and C#

© Geeks with Blogs or respective owner