Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Liskov Substitution

00:00 In the previous lesson, I covered the open-closed principle. In this lesson, I’ll show you the Liskov substitution principle. No rap songs were injured in the recording of this lesson.

00:12 The Liskov substitution principle is subtypes must be substitutable for their base types. It was introduced by Barbara Liskov at an object-oriented programming conference way back in 1987.

00:26 It’s now considered a fundamental concept of object-oriented code. Essentially, if a piece of code works with a class, it should be able to work with all the children of that class.

00:38 As a child inherits its parents’ members, as long as the child doesn’t break anything, you can be compliant with this principle by default. It’s related to a similar concept known as design by contract—the interface of a thing is how you interact with it—and as children get the same interface as their parent, they should be substitutable.

01:01 Consider a Bird class with a single method called .fly(). The Eagle is a child of Bird, so it gets the same method. Well, an ostrich is a bird, and now I’ve got that pesky .fly() method.

01:15 No problem. I’ll just have the child method override the parent and raise an exception. I could, but that would violate the LSP.

01:25 A better class structure for this problem is to separate out the flying feature. As there are birds that can’t fly, flying shouldn’t be an aspect of a bird.

01:34 By moving that into the FlyingBird class, this code no longer violates the LSP. If I add a Robin, it inherits FlyingBird, and whether I use an Eagle or a Robin, my interface is consistent. If I add a Penguin, it inherits directly from Bird, becoming a sibling of Ostrich, and the fact that it can’t fly doesn’t break the interface.

01:59 A common cause of violating the LSP is overthinking class hierarchies. It’s real easy to say, well, an ostrich is a bird, and an eagle is a bird, so I must need a bird class. In reality, you should be more concerned about the behavior of the class—i.e., what its methods are—than the physicality of the class.

02:18 If there isn’t any common code between an eagle and an ostrich, making them based on the same class just because they’re both birds is what’s getting in your way.

02:28 Some rules to consider are operations on a parent should be valid on a child—the .fly() example I just showed you. Methods on the child should take at least the same arguments as that of the parent. You can add more args, but the base should be the same.

02:45 Properties that are immutable on the parent should also be immutable on the child. Of course, problems could be rather subtle though. Let’s go look at a tougher example.

02:57 Yet another shape example, this time using rectangles and squares. This is a commonly used example to show when is-a doesn’t make much sense. My rectangle needs a width and a height.

03:11 My square only needs a side. In math, a square is a rectangle, so in theory, I can extend a rectangle, passing in the side for both the width and the height.

03:23 The problem here is those are both attributes. Some code somewhere might modify the width. Passing a square to that code breaks the idea of a square. Getting caught up in the is-a rather than focusing on the operations you wish to perform on the class leads you down a troubled road.

03:45 This course is based on a Real Python article. In it, the author does some fancier Python, making the argument that the LSP is being violated. Let me show you what he did, and then we can argue about the argument.

04:00 I’ve still got my square based on my rectangle, but now .__setattr__() is overridden. If you attempt to change the width or the height, they both get changed to the new value. This is a subtle thing.

04:14 I could argue it no longer violates the LSP. Anything you do to this square maintains its squareness, and because it’s done through the magic of .__setattr__(), anyone consuming the square doesn’t have to be aware of this. Now, I’m not arguing this is a good design, but for the life of me, I can’t come up with a counterexample where any operation that worked on the rectangle wouldn’t now work on the square.

04:38 But you’re jumping through a lot of hoops to make the statement that a square is a rectangle and gaining absolutely nothing from it. So, good code? Nope, not in the least. Violation of the LSP?

04:51 I’m not sure. Definitely a code smell though.

04:58 You’ve done SOL, now for the I in SOLID.

Become a Member to join the conversation.