Click here to Skip to main content
15,881,757 members
Articles / Web Development / ASP.NET

Understand Liskov Substitution Principle (LSP)

Rate me:
Please Sign up or sign in to vote.
4.51/5 (35 votes)
4 Nov 2018CPOL3 min read 123.1K   46   22
This article will help you to get a clear understanding of LSP.

Introduction

Here, I am going to discuss the Liskov’s Substitution Principle of S<code>OLID. Now what does SOLID mean? SOLID is an object oriented design principle, where each letter has it own meaning:

  • S-> Single responsibility
  • O-> Open Closed
  • L-> Liskov substitution
  • I-> Interface segregation
  • D-> Dependency inversion

According to Wikipedia, the definition of SOLID is:

"SOLID are guidelines that can be applied while working on software to remove code smells by causing the programmer to refactor the software's source code until it is both legible and extensible."

Background

If you read my previous article, it will be very helpful for you to understand SOLID.

  1. Understand Open Closed Principle and Dependecy Inversion .
  2. Understand Single Responsibility and Interface Segregation

Using the Code

Before starting with the technical discussion, I want to answer the below questions:

What is Liskov substitution principle?

Answer: "objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program"

Is my answer tough to understand? Ok, I will make it more easy...

"It means that we must make sure that new derived classes are extending the base classes without changing their behavior."

Let's consider an example to make it better understood. If you read articles related to the Liskov principle, one example is very popular. Yes that is Rectangular and Square. Here, I will show you the same but will try to explain each and every aspect clearly. Before starting with the technical discussion, look at the below picture and answer the questions.

           

Figure 1

Question: Is Square a Rectangle?
Answer: Yes

If you remember Inheritance, then the word "Is a" relationship familiar to you. So if I convert this to code, it looks like the following:

C#
public class Rectangle
{
    protected int _width;
    protected int _height;
    public int Width
    {
        get { return _width; }
    }
    public int Height
    {
        get { return _height; }
    }

    public virtual void SetWidth(int width)
    {
        _width = width;
    }    
    public virtual void SetHeight(int height)
    {
        _height = height;
    }
    public int getArea()
    {
        return _width * _height;
    }
}  

public class Square : Rectangle  // In an "is a" relationship, the derived class is clearly a
                                  //kind of the base class
{
    public override void SetWidth(int width)
    {
        _width = width;
        _height = width;
    }

    public override void SetHeight(int height)
    {
        _height = height;
        _width = height;
    }
}

So everything is ok. Now we will calculate the area of Figure 1: Rectangle and Square.

C#
public void AreaOfRectangle()
    {
        Rectangle r = RectangleFactory(); // Returns the rectangle type object
        r.SetWidth(7);
        r.SetHeight(3);
        r.getArea();
    }  

So can you tell me the output of the r.getArea(); method. Yes, very simple, the expected output is 7 * 3=21. Now run the program with the below RectangleFactory() method and see whether we get our expected result or not?

C#
public  Rectangle RectangleFactory()
    {
        return new Square();
    }

One thing I want to mention about the RectangleFactory() is that, this method is now exposed to you. But think as if you are getting the Rectangle object just by using a factory DLL or from any service where you have no idea what type of rectangle object will be returned.

Did you see the result? Yes.

This is the output: 9.

So what's wrong there ? Remember as I said earlier:

"we must make sure that new derived classes are extending the base classes without changing their behavior"

If we say specifically:

"we must make sure that Square classes are extending the Rectangle without changing their behavior"

Is the above sentence correct according to our program?

No, the problem is:

without changing their behavior

But we change the behavior of the base Rectangle class. Did you notice how? No, OK, let's see:

C#
public override void SetWidth(int width)
    {
        _width = width; 
        _height = width; //Change the behavior here by assigning the width to Rectangle _height
                         
    }

    public override void SetHeight(int height)
    {
        _height = height;
        _width = height;//Change the behavior here by assigning the height to Rectangle _width 
                        
    } 

Width=Height is must be rule for Square <code>not for Rectangle. So when Square object returns from Rectanglefactory() before calling getArea(), user assigns r.SetHeight(3) and r.SetWidth(7) and waits for a result 21 but gets 9. It changes the result. But the end user was expecting the result of Rectangle area.

So violation of Liskov substitution principle occurs.

Now the solution is to manage the class inheritance hierarchies correctly. Let's introduce another class.

C#
public abstract class Quadrilaterals
{
    abstract public int GetArea();
}

Now change the inheritance hierarchies by making it base class.

C#
public class Rectangle :Quadrilaterals
{    
    public int Width
    {
        get ; set ;
    }
    public int Height
    {
        get ; 
        set ;
    }
    public override int GetArea()
    {
        return Height * Width;
    }    
} 
C#
public class Square : Quadrilaterals  // In an "is a" relationship, the derived class is clearly a
                                      // kind of the base class
{
    public  int Size
    {
        get ; 
        set ;
    }
    public override int GetArea()
    {
        return Size* Size;
    }
}  

Now our factory method also changes.

C#
public Quadrilaterals QuadrilateralsFactory()
   {
       return new Square();
   }
   public void AreaOfQuadrilateral()
   {
       Quadrilaterals r = QuadrilateralsFactory(); // Returns the Quadrilaterals type object
       r.Height=7;
       r.Width=3;
       r.getArea();
   }

Points of Interest

So from users point of view, now s/he is expecting the area of <code>Quadrilateral not the Rectangle. By using this input, when output comes 21 is a Rectangle area and when 9 it's a Square .

C#
Quadrilaterals r = new Rectangle();
       r.Height=7;
       r.Width=3;
       r.getArea();

       Quadrilaterals r = new Square();
       r.Height = 7;
       r.Width = 3;
       r.getArea();

Now user gets the expected behavior from the program. So it satisfies the Liskov Substitute Principle (LSP).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Bangladesh Bangladesh
I am a Sr.Software Engineer at Brain Station -23. I have 5+ years of work experience in .Net technology. I strongly believe that before software can be reusable it first has to be usable.

My contact info :

mfrony2003@yahoo.com
mfrony2003@hotmail.com

LinkedIn
http://www.linkedin.com/profile/view?id=106671466&trk=tab_pro

Comments and Discussions

 
QuestionSOID Pin
dmjm-h5-Nov-18 13:04
dmjm-h5-Nov-18 13:04 
GeneralYour example should be like this. Pin
Sanjay K. Gupta5-Nov-18 2:55
professionalSanjay K. Gupta5-Nov-18 2:55 
QuestionAre you fitting square pegs into round holes? Pin
George Swan4-Nov-18 20:23
mveGeorge Swan4-Nov-18 20:23 
PraiseSimple and Well explained Pin
laxmeesh.joshi7-Mar-16 23:04
laxmeesh.joshi7-Mar-16 23:04 
General[My vote of 2] Good but... Pin
Nejimon CR1-Jan-16 1:44
Nejimon CR1-Jan-16 1:44 
GeneralRe: [My vote of 2] Good but... Pin
Member 1379719924-Apr-18 22:45
Member 1379719924-Apr-18 22:45 
GeneralRe: [My vote of 2] Good but... Pin
Paulo Zemek26-Jun-18 11:36
mvaPaulo Zemek26-Jun-18 11:36 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun1-May-15 0:00
Humayun Kabir Mamun1-May-15 0:00 
GeneralNicely explained! Pin
Shahdat Hosain10-Nov-13 13:01
Shahdat Hosain10-Nov-13 13:01 
GeneralMy vote of 3 Pin
Silvabolt16-Oct-13 3:58
Silvabolt16-Oct-13 3:58 
SuggestionExplaining LSP by sacrificing POLA principle. Pin
Ryszard Dżegan8-Sep-13 22:59
professionalRyszard Dżegan8-Sep-13 22:59 
GeneralRe: Explaining LSP by sacrificing POLA principle. Pin
VikasChaturvedi11-Jun-14 21:43
VikasChaturvedi11-Jun-14 21:43 
GeneralMy vote of 5 Pin
Mohammed Hameed22-May-13 19:17
professionalMohammed Hameed22-May-13 19:17 
QuestionI see where you are going but... Pin
MarkRHolbrook22-May-13 8:38
MarkRHolbrook22-May-13 8:38 
What I don't like is this part of your article:

C#
Quadrilaterals r = new Square();
        r.Height = 7;
        r.Width = 3;
        r.getArea();


By definition a Square has an equal Height and Width or for sides of equal length. It should be impossible to set them differently. If this code was stepped through in a debugger then at the instant the assignment of:

r.Height = 7;

occurs the area function would return 49. Then on the next line it suddenly changes to 9 because the setter methods are ensuring that both properties are the same. Also it opens the code to cut/paste errors. Assume a programmer is told to use the Rectangle class and make some rectangles of sizes 7, 3. Then he is told to use Square and make some 5 x 5 squares.

So he cuts Rectangle code, pastes it and changes the construction to Square. Now he has:

C#
Quadrilaterals r = new Square();
        r.Height = 7;
        r.Width = 3;
        r.getArea();


Being in a hurry to get to lunch or to meet his girlfriend he quickly changes r.Height to equal 5. Saves his work and moves on. The problem is that the following line of r.Width = 3 now makes his code wrong. His square is not 5 now but 3 in size.

Yes... it works... yes it may seem to solve the LSP problem. But in fact it violates in my opinion other principles in that if I want to set r.Height I should be able to assume that r.Width is not changing but it is. It also forces me to be careful where I should not have to be.

From a purely mathematical stand point a Square is an object with 4 sides of length "L". One possible way this could be represented would be to have objects something like: (I've left out many details for brievity sake and this is mostly psuedo code)

C#
class line
{
  int begX, begY;
  int endX, endY;
  int Length();
  { 
    return sqrt( (endX-begX)^2 + (endY-begY)^2 );
  }
}

class basepolygon
{
  line sides[];
  int GetArea()
  {
    return side[0].Length() * side[1].Length();
  }
  int GetPerimeter()
  {
    int perimeter = 0;
    for(int i=0; i<sides.count(); i++ )
      perimeter += sides[i].Length();
    return perimeter;
  }
}

class square : basepolygon
{
  square(x1,y1,S)
  {
    sides.add( new line(x1,y1,x1+S,y1) );
    sides.add( new line(x1+S,y1,x1+S,y1+S) );
    sides.add( new line(x1+S,y1+S,x1,y1+S) );
    sides.add( new line(x1,y1+S,x1,y1) );
  } 
} 

class rectangle : basepolygon
{
  rectangle(x1,y1,L,H)
  {
    sides.add( new line(x1,y1,x1+L,y1) );
    sides.add( new line(x1+L,y1,x1+L,y1+H) );
    sides.add( new line(x1+L,y1+H,x1,y1+H) );
    sides.add( new line(x1,y1+H,x1,y1) );
  }
}

class hexagon : basepolygon
{
  hexagon( *** parameters here to init a hexagon *** );
  override int GetArea()
  {
    *** calc area of your hexagon here
  }
}


etc...
You'd define a other polygons in the same way with more sides.

It suffices to say this works nicely for squares, rectangles and polygons since they are constructed from similar elements (lines). LSP is somewhat respected as well since you can pass a hexagon to something expecting a basepolygon and call GetArea() and it will work. This example fails because the contructors are different but in the real world they would be too.

But the circle/ellipse problem is not easy to solve. They are similar but just different enough that coming put with a scheme that keeps everything happy in LSP and OCP is difficult.

This is one of those things where in school you study this stuff until your eyes are red but in real life you end up making different objects because it takes far too much time and effort to try and spend the time to come up with a derivation that satisfies all the principles and in practice that perfect object exchangability we all strive for isn't always needed. It is a judgement call on how insanely far to carry some of the SOLID and other principles.
BugNot a good example of Liskov Pin
charoco22-May-13 7:51
charoco22-May-13 7:51 
GeneralMy vote of 3 Pin
Paulo Zemek21-May-13 10:23
mvaPaulo Zemek21-May-13 10:23 
QuestionThis is wrong. Pin
Paulo Zemek21-May-13 8:58
mvaPaulo Zemek21-May-13 8:58 
AnswerRe: This is wrong. Pin
Faisal(mfrony)21-May-13 14:28
Faisal(mfrony)21-May-13 14:28 
GeneralRe: This is wrong. Pin
Paulo Zemek21-May-13 15:17
mvaPaulo Zemek21-May-13 15:17 
GeneralRe: This is wrong. Pin
Faisal(mfrony)21-May-13 15:30
Faisal(mfrony)21-May-13 15:30 
AnswerRe: This is wrong. Pin
Faisal(mfrony)21-May-13 15:20
Faisal(mfrony)21-May-13 15:20 
GeneralExplained here Pin
RaviBattula21-May-13 0:02
professionalRaviBattula21-May-13 0:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.