Liskov Substitution Principle (The “L” in SOLID Programming Principles)
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