Geeks With Blogs

News

WTF Next? Dev ramblings from a master of nothing.

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 be a property provable about objects of type . Then should be provable for objects of type where is a subtype of .

#### 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?

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#

Related Posts on Geeks With Blogs Matching Categories

Comments on this post: Liskov Substitution Principle and the Oft Forgot Third Wheel

# re: Liskov Substitution Principle and the Oft Forgot Third Wheel
Nice post.

Some more thoughts.

The expectations on the parent is key - what does existing code expect from the parent class.

Ideally, these expectations are expressed explicitly in the form of pre-conditions, post-conditions and invariants. Chances are they are not so well formalized.

Worse yet, a developer may have peaked into the source code of the class being consumed and has now coded the consumer to rely on the private details. This breaking of encapsulation in the end result in a horrible *implicit* expectation being placed on the parent class.

You could argue that this developer deserves all he gets. But don't be so hasty - the parent class did not advertise its contract, leaving the developer of the consumer with little option but to peak at the privates.

Christian
Left by Christian Crowhurst on Jun 28, 2012 7:48 PM