The Liskov Substitution Principle (LSP) has important implications for software architecture.
You can find my previous articles on the LSP here and here.
At first glance, Liskov seems to be about how to structure our inheritance hierarchies. However, there is more to LSP than Object-Oriented (OO) Inheritance.
Previously, we derived child class Employee from base class Person. Instances of Employee inherited property Age from Person even though callers of Employee were not interested in Age. OO Inheritance hierarchies are unforgiving in that way—children will receive the cumulative public interfaces of all their ancestors.
This problem does not go away if, instead of class Employee inheriting from a concrete base class (Person), Employee implements an interface, IPerson. If IPerson demands the implementation of property Age, then Employee must still have an Age property.
In this way, the LSP applies to interfaces just the same as it does to inheritance.
Doesn’t Liskov mean that we can have ‘entirely substitutable implementers for an interface, and the running of a program is unchanged’?
That seems a pretty decent definition of pluggability: Plug in a subtype to the interface, and the program still works as expected.
Let’s invert this: If we use a subtype instance at runtime, and this subtype changes how the program operates, then is that a pluggable subtype?
No, it’s not.
The point of a pluggable architecture is that when we employ different versions of the same functionality—the subtypes, then the program’s operation ought to be unaffected.
OK, time for an example: We have an IPaymentGateway interface. The interface has a Pay() method. We have a PaypalPaymentGateway implementation of IPaymentGateway. PaypalPaymentGateway is used to pay for goods and services in an e-Commerce application. This e-Commerce application does not know (or care) that at runtime, it specifically uses instances of PaypalPaymentGateway for references of IPaymentGateway.
StripePaymentGateway is another IPaymentGateway implementation.
Let’s assume that in our e-Commerce program, we now unplug PaypalPaymentGateway and switch in StripePaymentGateway. Liskov implies that as long as the high-level, programmatic workflow is unaware of the change in IPaymentGateway implementer, everything is hunky-dory.
OK, let’s make things interesting. What if we used BadPaymentGateway in our e-Commerce application? Class BadPaymentGateway implements IPaymentGateway. However, BadPaymentGateway throws an exception when the Pay() method is called. Will the system run as before? No, it won’t. Unlike the other payment gateway classes, the program will operate differently: It will be interrupted by the exception.
With the well-behaved payment gateway subtypes, the program was not interrupted by exceptions. We have changed the operation of the program. We have lost Pluggability.
A non-programming example: We have a problematic toaster. Every time we try to use this toaster, it shortcircuits and blows a fuse. When using this toaster, we break the electrical circuitry of the house. This toaster—a pluggable subtype—changes the operation of the electrical system—the program.
The Liskov Substitution Principle is not a mere sideshow to the ‘more important’ SOLID Principles. It’s a guiding light on how we ought to design our systems for pluggability.
LSP & Architecture
/by Olaf ThielkeLiskov & Architecture
The Liskov Substitution Principle (LSP) has important implications for software architecture.
You can find my previous articles on the LSP here and here.
At first glance, Liskov seems to be about how to structure our inheritance hierarchies. However, there is more to LSP than Object-Oriented (OO) Inheritance.
Previously, we derived child class
Employee
from base classPerson
. Instances ofEmployee
inherited propertyAge
fromPerson
even though callers ofEmployee
were not interested inAge
. OO Inheritance hierarchies are unforgiving in that way—children will receive the cumulative public interfaces of all their ancestors.This problem does not go away if, instead of class
Employee
inheriting from a concrete base class (Person
),Employee
implements an interface,IPerson
. IfIPerson
demands the implementation of propertyAge
, thenEmployee
must still have anAge
property.In this way, the LSP applies to interfaces just the same as it does to inheritance.
Doesn’t Liskov mean that we can have ‘entirely substitutable implementers for an interface, and the running of a program is unchanged’?
That seems a pretty decent definition of pluggability: Plug in a subtype to the interface, and the program still works as expected.
Let’s invert this: If we use a subtype instance at runtime, and this subtype changes how the program operates, then is that a pluggable subtype?
No, it’s not.
The point of a pluggable architecture is that when we employ different versions of the same functionality—the subtypes, then the program’s operation ought to be unaffected.
OK, time for an example: We have an
IPaymentGateway
interface. The interface has aPay()
method. We have aPaypalPaymentGateway
implementation ofIPaymentGateway
.PaypalPaymentGateway
is used to pay for goods and services in an e-Commerce application. This e-Commerce application does not know (or care) that at runtime, it specifically uses instances ofPaypalPaymentGateway
for references ofIPaymentGateway
.StripePaymentGateway
is anotherIPaymentGateway
implementation.Let’s assume that in our e-Commerce program, we now unplug
PaypalPaymentGateway
and switch inStripePaymentGateway
. Liskov implies that as long as the high-level, programmatic workflow is unaware of the change inIPaymentGateway
implementer, everything is hunky-dory.OK, let’s make things interesting. What if we used
BadPaymentGateway
in our e-Commerce application? ClassBadPaymentGateway
implementsIPaymentGateway
. However,BadPaymentGateway
throws an exception when thePay()
method is called. Will the system run as before? No, it won’t. Unlike the other payment gateway classes, the program will operate differently: It will be interrupted by the exception.With the well-behaved payment gateway subtypes, the program was not interrupted by exceptions. We have changed the operation of the program. We have lost Pluggability.
A non-programming example: We have a problematic toaster. Every time we try to use this toaster, it shortcircuits and blows a fuse. When using this toaster, we break the electrical circuitry of the house. This toaster—a pluggable subtype—changes the operation of the electrical system—the program.
The Liskov Substitution Principle is not a mere sideshow to the ‘more important’ SOLID Principles. It’s a guiding light on how we ought to design our systems for pluggability.