SOLID Development: Liskov Substitution Principle

0
337

Continuing on our series on SOLID development practices, in this guide we’re going to walk through the Liskov substitution principle.

liskov substitution principle

When it comes to object oriented development, the Liskov substitution principle can be slightly confusing. And part of the confusion comes from the fact that this principle has more of an effect with statically typed languages, such as Java. However it’s still an important concept to understand, so stay tuned and I’ll walk us through a practical example of how this applies to all OOP languages.

Liskov Substitution Principle Definition

To be 100% transparent, I struggled through researching this topic. But what helped me understand it was:

  1. Talking it through with a software engineer that I respect (Chase Baker).
  2. Breaking down the definition into very small pieces.

With that in mind let’s walk through a dead simple definition of the concept. The Liskov substitution principle states that:

A program should have the ability to replace any instance of a parent class with an instance of one of its child classes without negative side effects.

Liskov Substitution Principle Breakdown

If that’s about as clear as mud don’t worry, this principle isn’t for the faint of heart. It helped me tremendously to break the definition apart, so let’s give that a shot.

We’ll start with the fact that we know we’re going to be working with replacing instances of classes.

Next we know that we’re going to be working with parent and child classes. This tells me that the principle revolves around object oriented inheritance.

Lastly it sounds like programs have to be able to allow for child class instances to seamlessly replace parent classes. This tells me that we need to focus on the messages that are sent, along with ensuring that our parent and child classes can’t have requirements that would cause conflicts.

Still confused? That’s fine, this concept took me longer to understand than all of the other SOLID principles combined. So you’re in good company.

Liskov Substitution Principle Example

liskov substitution principle

For our code walk through I created a basic User class in Ruby. Additionally I build an AdminUser class that inherits from the parent User class. The classes have attributes for settings and an email.

In our case study we decided to make an interesting decision to use the Hash data type for our settings in the User class. However we’re using the Array data type for our AdminUser class settings. This seems like an innocuous issue because if we run this program both classes seem to be working fine.

liskov substitution principle

The Problem

liskov substitution principle

Our problem may not be evident quite yet. However watch what happens when we need to build a new feature. Our new feature is a script called signed_in_today? that will run each day and generate a report that lists out which users logged in that day. It combines both regular and admin users into an instance variable called @user_database. From there is loops over the settings for all of the users to see if the user signed in that day or not.

This is a pretty common feature, but let’s see what happens when we run this code:

liskov substitution principle

Our script works perfectly fine for our user that was created straight from the User class. However it breaks when it comes to our admin user and gives the error no implicit conversion of Symbol into Integer (TypeError).

The bug with this program is relatively straightforward. Our signed_in_today? method is looking for settings that are stored as a Hash data type. But when it encounters the AdminUser settings, which are stored with the Array data type it throws an error.

Liskov Substitution Principle Violation

So does this example qualify as a Liskov substitution principle violation? Let’s look at the definition again:

A program should have the ability to replace any instance of a parent class with an instance of one of its child classes without negative side effects.

In our example we attempted to substitute an instance of a child class, our AdminUser, for its parent class. And by performing this action it broke the program, which I think qualifies as a negative side effect.

The Fix

Now let’s see what we need to do to fix this. You may think it’s as easy as changing the admin settings call so that we pass it a hash, like this:

liskov substitution principle

And yes, technically that would work. For this one instance. However this doesn’t fix the core problem and this bug will continue to happen if a developer forgets that settings need to be a hash. So what’s a better solution? Personally I’d attack the root issue and force the program to only have one options when it comes to saving settings.

liskov substitution principle

In this code I’m importing the OpenStruct library. Next I remove settings as an attribute of the class and instead I create a method called set_settings that can be called and saves the setting into the OpenStruct data type. Then settings can be called from anywhere in the application for the User class and any child classes it may have, and we can trust the behavior that it will generate.

Now our AdminUser class instances can replace any instances of the User class and the program will still work properly.

Summary

I hope that this has been a helpful guide to the Liskov substitution principle and that you feel more confident with how you can apply the concept to your own work.

LEAVE A REPLY

Please enter your comment!
Please enter your name here