Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
November 14, 2020 04:00 pm GMT

Liskov Substitution Principle

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 principles name sounded very strange to me. I judged the book by its cover, and I convinced myself that I wouldnt grasp it. Eventually, it turned out that it was one of the easiest and straight forward principles in SOLID principles.

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

Its 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 lets 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, weve 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. Thats why the principle has the keyword substitution in its name. As for Liskov, its 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, lets try to link the definition weve 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.

public class Bird {    public void eat() {        System.out.println("I can eat.");    }    public void fly() {        System.out.println("I can fly.");    }}
Enter fullscreen mode Exit fullscreen mode

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

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!");    }}
Enter fullscreen mode Exit fullscreen mode

bird-swan-2

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.

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);    }}
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

Now, lets 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.

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

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

I believe I can fly!
Enter fullscreen mode Exit fullscreen mode

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?

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!");    }}
Enter fullscreen mode Exit fullscreen mode

bird-swan-penguin-1

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.

public static void main(String[] args) {    List<Bird> birds = new ArrayList<Bird>();           birds.add(new Swan());    birds.add(new Penguin());    letBirdsFly(birds);}
Enter fullscreen mode Exit fullscreen mode
I believe I can fly!Exception in thread "main" java.lang.UnsupportedOperationException: Help! I cannot fly!
Enter fullscreen mode Exit fullscreen mode

Ops! it didnt 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 didnt use inheritance correctly, hence caused a problem. The Penguin tries to extend the flying logic, but it cant fly!

We can fix this problem using the following if check:

public static void letBirdsFly(List<Bird> birds) {    for(Bird bird: birds) {        if(!(bird instanceof Penguin)) {            bird.fly();        }    }}
Enter fullscreen mode Exit fullscreen mode

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 were 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.

public class Bird {    public void eat() {        System.out.println("I can eat.");    }}
Enter fullscreen mode Exit fullscreen mode
public class FlyingBird extends Bird {    public void fly() {        System.out.println("I can fly.");    }}
Enter fullscreen mode Exit fullscreen mode
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!");    }}
Enter fullscreen mode Exit fullscreen mode
public class Penguin extends Bird {    @Override    void eat() {        System.out.println("Can I eat taco?");    }}
Enter fullscreen mode Exit fullscreen mode

bird-swan-penguin-flyingbird-3

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

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);    }}
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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

Weve 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!


Original Link: https://dev.to/amrsaeedhosny/liskov-substitution-principle-ofc

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To