Liskov Substitution Principle (The “L” in SOLID Programming Principles)

Bryon Harris
arcanium
Published in
10 min readMar 14, 2023

The Liskov Substitution Principle is the “L” in SOLID Programming.

Use LSP to guide inheritance and to design better architectures. It extends the Open-Closed Principle (the “O” in SOLID) to substituting subtypes for base types.

I’ve shared 3x examples to help you understand this tricky concept. Let’s get into it!

The Book: https://amzn.to/3GPypMA

Transcripts (the following transcripts are auto-generated and may have typos or inaccuracies):

all right welcome back to the next

episode in our series on the solid

principles for object-oriented

programming you can apply these

principles across software engineering

in general to help you conceive better

um programming and better code on

your journey as a software program or an

architect, they can be applied to the

whole architecture to modules to

components

um and today we are going from the s to

the O and finally to the L which is the

LSP or Liskov substitution principle so

let’s get into it arcanium

welcome to a production by Dr Miles

Aaron

CEO and co-founder at Arcadian Ventures

don’t forget to subscribe subscribe

subscribe

[Music]

Barbara liskovs coined the term uh the

liskov substitution principle in 1988.

the LSP tells us that subtypes must be

substitutable for their base types so

what does this mean it’s kind of an

extension of the open closed principle

that we talked about last time to this

idea of subclasses or subtypes so in the

object oriented World we’ll refer to

classes but this can be extended to

types in other paradigms as well

and when we refer to interfaces

um throughout this video those

interfaces

um could be a class interface in the

object-oriented Paradigm or it could

even be the interface to a rest API so

you can abstract these ideas and apply

them across your programming when we say

that subtypes must be substitutable for

their base types what we’re really

referring to is the conditions that you

should place on the inputs and outputs

of a subtype and so your subclass is

going to have inputs and outputs just

like the base class

and what the LSP says is that you

shouldn’t make the input parameters the

rules governing those input parameters

any more strict than on the base class

and that your

output or return value parameters should

be at least as strict so you can make

those strict more strict but they should

be at least as strict as your base class

so we’re going to go through some real

examples to to make this a little easier

to understand one thing that I’ll

mention before we jump into the examples

is that unlike some of the other

principles here it’s it’s going to be

hard for a compiler to catch these

errors so you’re going to need to rely

on code reviews and on

uh unit tests and and different tests to

make sure that you actually

um don’t violate the LSP in practice now

I think there will be ways to automate

it depending on the language you’re

using but in general

um that’s why it’s so important to learn

this because it’s going to save you a

headache but it’s also one of those

things that’s a little harder to

automate away as a potential problem so

the classic example that’s thrown around

all the time is this idea of

um the class of a rectangle versus a

square so if if you have a user class

and the user can Implement a rectangle

or use this rectangle class

and we say okay the rectangle on the

rectangle you can set a height and you

can set a width

well uh square is also a rectangle so

let’s imagine that the square is a

subtype of the rectangle on the Square

though when you set the width it’s going

to set both the width and the height to

the same value and if you set the height

it’s going to set both the width and the

height again to the same value that’s

not always the case though for a

rectangle so if the square is a subtype

of the rectangle and the user goes to

use that rectangle but expects it to

behave like a rectangle and now you

substitute in a square what’s going to

happen is that when they call set height

and they call the set width

methods that they they expect the

rectangle to have it’s not going to

behave the way they expect because let’s

say they set width and height and they

grab those two values and they calculate

area

well whatever they set last is going to

set both sides and it’s going to end up

with a value that um you know doesn’t

doesn’t do what you expect it to do it’s

going to fail your assertions fail your

tests

um and result in uh broken application

or an application that does not behave

the way you expect it so this is one of

the concepts of LSP

um

is that if you substitute a subtype for

the base type the application should

should still behave the same and in this

case it wouldn’t so let’s go over

another example to help this kind of set

in because to be honest none of us are

playing with squares and rectangles

generally we need to move towards

something a little more real actually

let’s do one more silly example just to

help this set in so I saw this example

on stack Overflow I thought it was um

helpful so I hope you do too

um so let’s say you have a class called

bird and

um you want to

um create a subclass that extends that

class called duck and bird has a method

called fly now when you know duck is a

bird and so you you use this subclass

and ducks can also fly so great it works

now let’s say

um you want to classify an ostrich as a

bird as well so you use your bird class

and you say you know ostrich extends

bird great but ostriches can’t fly

so now you need some logic in your bird

class that says hey birds can fly but if

their ostriches actually they can’t and

that is a violation of LSP because now

your bird class has to be aware of which

subtype it’s using and for LSP to work

it shouldn’t care which subtype it’s

using you should be able to substitute

any subtype for that base type so the

ostrich violates the bird so how could

we fix that bird example well what if we

created a a class in between our ostrich

and Duck and our bird and we called it

flying bird and we could say that um you

know duck extends to flying bird and

flying bird has the fly method

um and we could say that ostrich extends

bird which doesn’t have the flying

method now we can add all of our flying

birds to this to extend this flying bird

class which extends as the bird class

and we could use the

bird class just directly for our

non-flying birds and we’re not violating

LSP okay so in both of these examples

what we did was very simple a bit

contrived and it worked to show us kind

of how to how to choose how to design

classes for inheritance and so what we

did in these examples was very simple

and a little bit contrived it showed us

how to use inheritance when designing

classes but of course these things can

be extended further so let’s take it one

step closer to a real system all right

so most of us at some point in our

careers have implemented some sort of

payment Integrations right let’s imagine

imagine that we’re building a banking

withdrawal system okay so this is going

to be a class that’s used to withdraw

from from a given account

now the subclasses that we’re going to

use are going to be the accounts and

we’re going to see if we can design a

system that accounts for some different

scenarios that we might run into with

bank accounts

so with the first one we’ll imagine a

simple checking account

and that checking account

um

is is going to be you’re going to be

able to withdraw money from it so no

problem it can use this banking withdraw

class that has a withdrawal method right

now let’s imagine that we have another

type of account and it’s a savings

account

okay fine checking and savings word bank

it’s all makes sense so far right

all right so

because we have those two accounts and

they’re connecting directly to our

withdrawal service we say hey you know

what let’s put an account class in

between so that

um the different types of account can

um can extend the account class and that

way we can use LSP and we can add lots

of different accounts and all the um the

withdrawal service needs to know is that

the

um that the account uh will have certain

inputs and outputs and that um we will

be able to withdraw from it and this is

basically the open closed principle

right we wanted we want to close

modification in our banking withdrawal

system so we put that interface of the

account there to um to allow us to have

different accounts without the banking

withdrawal system being open for

modification that’s just the ocp or the

O and solid that we talked about last

time

all right so now we’re gonna throw in a

little twist

um what happens if now our savings

account is a fixed term uh investment

account savings account right so the

bank says hey you can put your money in

here and we’re going to give you a high

interest rate but you can’t take it out

for two months something like that or a

year like a typical CD or something

maybe there’s a way to withdraw it but

you can’t just withdraw it with the

normal banking withdraw method maybe you

have to go in person and sign something

and it’s a different method right

um okay so our savings account

um Now does not always have the ability

to do a withdrawal and so we go up to it

it goes up to our account

um class and that is expecting to work

with any of those sub subtypes that

should be able to be substituted in

there we go to withdraw

and the whole application fails

okay so so what happened there same

thing as with the Ducks and the birds

and with the squares and the rectangles

we violated LSP

um because we have now a subtype that

doesn’t fit

um

we now have a subtype that can’t be

substituted for the base type

so

um

in the design of this system we need to

go a little further to conserve the

value we got from using the ocp by

closing changes on that banking

withdrawal system

and in order to do that we can’t just

simply have all of our accounts account

types attached to account and then to

withdraw we need to create a more

complex architecture

and in this case one way you could do

that would be to kind of hoist that

account interface up and have a

withdrawable account

um subtype that could include your

different types of checking accounts

that can all do immediate withdrawals

maybe a standard savings account they

can do an immediate withdrawal and

um your bank withdraw class can use the

withdrawable account but then also from

account you can have non-withdrawable

accounts and those can include things

like your fixed term savings account

um which will no longer be have direct

access to banking withdrawal system and

so by designing the system in that way

you were able to use

um the liskov substitution principle or

LSP to get the benefits of

um protecting your withdrawal system

from modification while being able to

use inheritance in a clever way to

simplify fi your code and keep things

nicely changeable so that you can add

new types of withdrawable accounts very

easily and you can add non-withdrawal

accounts very easily without having to

worry about adding complex logic to your

banking withdrawal class or breaking

your whole system and preventing people

from getting their money all together

so as you go deeper into software

architecture you’re going to see

interfaces in lots and lots of places

not just in classes one common example

you see all the time as a programmer is

an API spec it’s going to tell you

exactly what those inputs and return

types will be for a selection of API

endpoints and if you want that API to be

used by many clients they need to follow

that spec or you can run into similar

problems where you have a system that’s

built on an API it gets swapped out by a

different

um a different API connector and that

connector here is just like the subclass

or the subtype that uh Barbara told us

about and suddenly the whole system that

was substituted in breaks because it

didn’t adhere to the LSP as it works

with this API interface all right so I

hope that was understandable I know this

was a little bit more complex but I

think hopefully going through with um

squares and rectangles and ducks and

birds and Banking and apis made this

complex idea a little more simple it’s

really just an extension of the open

closed principle which where we’re just

talking about kind of connections

between single modules to this idea of

subtypes or subclasses

so if you have any questions please let

me know in the comments and I’ll try to

elucidate everything for you, hopefully

you understand clearly, and you’re ready

to move on to the I in solid in our next

video, so I’ll see you next time

--

--