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.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply