Liskov Substitution Principle
Tags: java, oop, programming, computerscience
Published: 2020-11-14 16:00:19

You know, when I heard the name of the Liskov Substitution Principle for the first time, I thought it would be the most difficult one in SOLID principles. The principle’s name sounded very strange to me. I judged the book by its cover, and I convinced myself that I wouldn’t grasp it. Eventually, it turned out that it was one of the easiest and straight forward principles in SOLID principles.

So, let’s start our journey by putting a simple definition for the Liskov Substitution Principle:

> It’s the ability to replace any object of a parent class with any object of one of its child classes without affecting the correctness of the program.

I know it sounds strange to you but let’s break it into pieces. Suppose we have a program that has a parent class. The parent class has some child classes who inherit from. If we decided to create some objects from the parent class in our program, we’ve to be able to replace any one of them with any object of any child class, and the program should work as expected without any errors.

In other words, we have to be able to substitute objects of a parent class with objects of child classes without causing the program to break. That’s why the principle has the keyword ‘substitution’ in its name. As for Liskov, it’s the name of the scientist Barbara Liskov who developed a scientific definition for that principle. You can read this article Liskov substitution principle on Wikipedia for more information about that definition.

Now, let’s try to link the definition we’ve just discussed to a famous example to understand the principle.

`Bird` is a class which has the two methods `eat()` and `fly()`. It represents a base class that any type of bird can extend.

```java
public class Bird {

	public void eat() {
		System.out.println("I can eat.");
	}

	public void fly() {
		System.out.println("I can fly.");
	}
}
```

`Swan` is a type of bird that can eat and fly. Hence, it has to extend the `Bird` class.


```java
public class Swan extends Bird {

	@Override
	public void eat() {
		System.out.println("OMG! I can eat pizza!");
	}

	@Override
	public void fly() {
		System.out.println("I believe I can fly!");
	}
}
```

![bird-swan-2](https://dev-to-uploads.s3.amazonaws.com/i/qgfbqwpynga5wya349wg.png)

`Main` is the main class of our program which contains its logic. It has two methods, `letBirdsFly(List<Bird> birds)` and `main(String[] args)`. The first method takes a list of birds as a parameter and invokes their fly methods. The second one creates the list and passes it to the first.

```java
public class Main {
	
	public static void letBirdsFly(List<Bird> birds) {
		for(Bird bird: birds) {
			bird.fly();
		}
	}

	public static void main(String[] args) {
		List<Bird> birds = new ArrayList<Bird>();
		birds.add(new Bird());
		letBirdsFly(birds);
	}
}
```

The program simply creates a list of birds and lets them fly. If you try to run this program, it will output the following statement:

```
I can fly.
```

Now, let’s try to apply the definition of this principle to our main method and see what happens. We are going to replace the `Bird` object with the `Swan` object.

```java
public static void main(String[] args) {
	List<Bird> birds = new ArrayList<Bird>();		
	birds.add(new Swan());
	letBirdsFly(birds);
}
```

If we try to run the program after applying the changes, it will output the following statement:

```
I believe I can fly!
```

We can see that the principle applies to our code perfectly. The program works as expected without any errors or problems. But, what if we tried to extend the `Bird` class by a new type of bird that cannot fly?

```java
public class Penguin extends Bird {
	
	@Override
	public void eat() {
		System.out.println("Can I eat taco?");
	}

	@Override
	public void fly() {
		throw new UnsupportedOperationException("Help! I cannot fly!");
	}
}
```

![bird-swan-penguin-1](https://dev-to-uploads.s3.amazonaws.com/i/0s69e85qxs4r362ndo86.png)

We can check whether the principle still applied to our code or not by adding a `Penguin` object to the list of birds and run the code.

```java
public static void main(String[] args) {
	List<Bird> birds = new ArrayList<Bird>();		
	birds.add(new Swan());
	birds.add(new Penguin());
	letBirdsFly(birds);
}
```

```
I believe I can fly!
Exception in thread "main" 
java.lang.UnsupportedOperationException: Help! I cannot fly!
```

Ops! it didn’t work as expected!

We can see that with the `Swan` object, the code worked perfectly. But with the `Penguin` object, the code threw `UnsupportedOperationException`. This violates the Liskov Substitution Principle as the `Bird` class has a child that didn’t use inheritance correctly, hence caused a problem. The `Penguin` tries to extend the flying logic, but it can’t fly!

We can fix this problem using the following if check:

```java
public static void letBirdsFly(List<Bird> birds) {
	for(Bird bird: birds) {
		if(!(bird instanceof Penguin)) {
			bird.fly();
		}
	}
}
```

But this solution is considered a bad practice, and it violates the Open-Closed Principle. Imagine if we add another three types of birds that cannot fly. The code is going to become a mess. Notice also that one of the definitions for the Liskov Substitution Principle, which is developed by Robert C. Martin is:

> Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

This is not the case with our solution, as we’re trying to know the type of the `Bird` object to avoid the misbehavior of the non-flying birds.

One of the clean solutions to solve this issue and refollow the principle is to separate the flying logic in another class.

```java
public class Bird {

	public void eat() {
		System.out.println("I can eat.");
	}
}
```

```java
public class FlyingBird extends Bird {

	public void fly() {
		System.out.println("I can fly.");
	}
}
```

```java
public class Swan extends FlyingBird {

	@Override
	public void eat() {
		System.out.println("OMG! I can eat pizza!");
	}

	@Override
	public void fly() {
		System.out.println("I believe I can fly!");
	}
}
```

```java
public class Penguin extends Bird {

	@Override
	void eat() {
		System.out.println("Can I eat taco?");
	}
}
```

![bird-swan-penguin-flyingbird-3](https://dev-to-uploads.s3.amazonaws.com/i/zhm1p05yx6a6vthowbjw.png)

Now we can edit the `letBirdsFly` method to support flying birds only.

```java
public class Main {
	
	public static void letBirdsFly(List<FlyingBird> flyingBirds) {
		for(FlyingBird flyingBird: flyingBirds) {
			flyingBird.fly();
		}
	}

	public static void main(String[] args) {
		List<FlyingBird> flyingBirds = new ArrayList<FlyingBird>();		
		flyingBirds.add(new Swan());
		letBirdsFly(flyingBirds);
	}
}
```

The reason we forced the `letBirdsFly` method to accept flying birds only is to guarantee that any substitution for the `FlyingBird` will be able to fly. Now the program works as expected and outputs the following statements:

```
I believe I can fly!
```

You can see that the Liskov Substitution Principle is about using the inheritance relationship in the correct manner. You’ve to create subtypes of some parent if and only if they’re going to implement its logic properly without causing any problems.

We’ve reached the end of this journey, but we still have another two principles to cover. So take your time reading about this principle and make sure that you understand it before moving on. Stay tuned!