SO[L]ID: The Liskov Substitution Principle (LSP)

How Bill Murray's time on the Tune Squad taught me about LSP

Posted on 07/23/2021 by Kasey Wahl
...

Dear Coder,

Last week I wrote to you about the [S] and the [O] in the SOLID acronym of software design principles.

This week, I watched Space Jam.

But I also studied the hell out of Liskov's Substitution Principle, or the [L] in SOLID.

I don't know if you've ever found yourself obsessing over two things at the same time, dear Coder. If you have, you might understand the phenomenon that I'm about to describe. 

I won't lie. LSP took a little time for me to wrap my mind around.

In definition, it's a little convoluted:

"If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program."

Dafuq?

I studied this definition over and over trying to make sense of it. I wikipedia'd it until I was blue in the face. I watched explainer after explainer until I got frustrated, gave up, and watched Space Jam.

Oh Space Jam--a truly transcendent film in which Michael Jordan is sucked into a golfball hole to get recruited by the Looney Tunes to play in a basketball game against a team of intergalactic villains, The Monstars.

There's a scene in Space Jam where the Monstars have so viciously battered Michael Jordan's Tune Squad that injuries have left the Tune Squad with four players fit to play basketball. If they can't find a replacement, they'll be forced to forfeit the game.

That's when Bill Murray hilariously shows up out of nowhere as a substitution.

Wait...

Substitution...

Liskov...

If Bill Murray is a subtype of Player...

Suddenly, my synapses were firing. It wasn't that Liskov's Substitution Principle was too difficult; it was that I hadn't been shown a good illustration of what it MEANS.

I ran to my computer to code up a scenario.

In this simple console application, I've re-created the scene from Space Jam (with a couple tweaks to make the analogy work better). 


When the program runs, it checks which of the starting players are eligible to play using the ShouldPlay method.

The player class contains the basic attributes of any player with a method to help determine if a player is eligible to play and a method to substitute in for another player.


I've also created sub-classes for each of the position groups in basketball: Point Guard, Shooting Guard, Small Forward, Power Forward, and Center...and Billy Murray.


Perfect. Let's run the console application and see what our lineup looks like.


It looks like Bugs Bunny has fouled out. No worries, though. We'll just use Bill Murray's Substitution method to substitute him into the game.

There's just one problem:

   

  

When I try to substitute Bill Murray in, I get an invalid operation exception.

Why is this?

Let's revisit what Liskov's Substitution Principle says:

"If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program."

Let's play a game that I was GREAT at when I was 3 years old. Which one of these subclasses is not like the other?

PointGuard : Player
ShootingGuard : Player
SmallForward : Player
PowerForward : Player
Center: Player
BillMurray : Player

Bill Murray is not a position group. He's just...Bill Effing Murray. Therefore, he's going to have some properties that differ from these position groups.

Liskov's Substitution Principle simply suggests that we play "which one of these is not like the other" when our sub-classes inherit from their parents. 

For this illustration, BillMurray's substitution threw an error because, even though he shares a lot of properties with the base Player class, an object of type "Bill Murray" should not be subbing in for an object of type "Power Forward." In other words, Bill Murray shouldn't directly inherit directly from the Player class because he's not really a player.

But we still need to substitute him into the game.

How can we refactor this code to satisfy Liskov's Substitution Principle?

One word, four syllables: Interfaces.

I've implemented an interface IPlayer with the properties and methods any player (including Bill Murray) should have, but it's concrete class is now called BasePlayer.


Then, I've implemented an Interface called IBillMurray that inherits from IPlayer that contains a string array called "skills". Each of my position groups also inherits from IPlayer now, with their own string arrays of Skills.


I've done this in order to decouple the "skills" that are unique to Bill Murray from the skills that are unique to each position group. This allows my code to be more modular and each class/interface can perform everything it needs to do without throwing errors. 


This interfacing allows me to substitute Bill Murray in for a point guard without violating Liskov's Substitution Principle. 


Thanks for sticking with me through this exhaustive example, dearest Coder. I hope LSP is a little more clear for you, but if you'd like to study up on it more or review the code that I wrote for this example, I've listed links below.

Get the Code here: Example One | Example Two

More on Liskov's Substitution Principle:

Tim Corey on LSP

Stack Overflow Example

Web Dev Simplified on LSP

Wikipedia Definition


That's all for me today, dear Coder.

Until next time, godspeed in your keystrokes.


Clickity Clacks,

Kasey





1 Comment(s)