This is the fourth in a series of (hopefully) amusing posts into Robert Martin’s SOLID principles. It’s written as a dialog between Socrates and a student named Loej in the spirit of the dialectic method popularized by Plato. In this post, the principle explored is the “Liskov Substitution Principle”. I hope you find this enjoyable.
Socrates, who is the narrator of the dialogue.
Loej, who is but a tiny minnow in a sea of 1’s and 0’s
Socrates: Welcome back Loej. Last time we discussed the Open / Closed principle.
Loej: Yes. We agreed that software entities should be open for extension but closed for modification. In .NET, careful use of Interfaces help us achieve this goal.
Socrates: Excellent. Today we will discuss the ‘L’ in Solid, the Liskov substitution principle.
Loej: Oh, I thought you sneezed.
Socrates: The Liskov substitution principle states that, if for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
Loej: Got it.
Loej: Yes. It makes perfect sense. Crystal clear. I think we’re done.
Socrates: Well what does it mean then?
Loej: It means that if for each o1 (mumble) such that (mumble mumble) the behavior of (mumble) and Q.E.D. Can I go now?
Socrates: But you just repeated what I said, more or less. Look this one is fairly straightforward.
Loej: Alright then. Let’s get on with it.
Socrates: According to Liskov if in all cases I can substitute type A for type B then A is a subset of B. So If I could substitute Circles, Rectangles, and Squares for…
Socrates: Or snakes, sharks, and monkeys for…
Socrates: I was thinking animals. But if I can substitute one for the other,** then I have a subtype to type relationship.
Loej: But this is just basic tenant of object oriented programming. A Cat IS AN animal. A Circle IS A shape.
Socrates: So then anywhere I expect an animal, I should be able to pass a cat, or a brontosaurus, or any other animal?
Loej: Yes. A brontosaurus **IS A dinosaur which IS AN animal. You can pass them to a function that takes animals without a problem. That’s a basic property of polymorphism. I don’t see anything terribly profound here.
Socrates:** Well, there is an important corollary which can be derived from the Liskov principle. Anywhere code expects a base type, the program shouldn’t need to know it’s getting a derived type.
Loej: So if our function takes an Animal it shouldn’t need to know that it’s really getting a Cat? But why not?
Socrates: What would happen if we added a Dog type?
Loej: I see. We might find that we had to modify the function that took an Animal to update its behavior for the Cat type in case it overlaps with the Dog type.
Socrates: Right and this violates the Open/Closed principle. But there is one more serious problem. If our function takes an Animal and then interrogates its subtype to discover how to handle the animal, the base type has taken a dependency on its subtype!
Loej: That would be bad.
Socrates: There’s an even more subtle side to the Liskov substitution principle.
Loej: What’s that?
Socrates: Just as it would be incorrect for a subtype to dictate its parent’s behavior, it is incorrect to assign a subtype that cannot guarantee its parent behavior.
Loej: But that’s not possible with an IS A relationship, right?
Socrates: Actually Bob Martin’s paper on the Liskov substitution principle discusses this in depth. IS A doesn’t guarantee behavior of subtypes conform to base types. To really get inheritance right takes some deeper thought.
Loej: If I can’t depend on IS A what can I depend on?!?
Socrates: Well, usually IS A ‘IS A’ valid criterion. Most of the time two classes in an IS A relationship will have the same behavior as you move down the inheritance tree. But think before your inheriting. Make sure that behavior and constraints are enforceable down the tree.
Loej: Well I definitely have some thinking to do.
Socrates: Until next time my friend.