The Effect of Liskov on 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 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.
Leave a Reply
Want to join the discussion?Feel free to contribute!