Saturday, December 10, 2016

LSP - Liskov's Substitution Principle

LSP is an extension to OCP and it says - Derived class (sub class) must be completed substituted for it's base class (parent class).

This means that we must ensure that new derived classes extend the base classes without changing their behaviour.

See the example explained in OCP where Rectangle, Circle, Square are being substituted for Shape class without changing it's behaviour.  


Example where LSP is violated: 
  • Lets say we have an application which uses Rectangle class as described below:
  • The application works well and installed in may sites. Lets say one day user demands to have Squares along with Rectangles in the application. In mathematics a Square is an specialisation of a Rectangle which makes an "IS A" relationship, and makes us model this with an Inheritance. So in our code we made Square inherit from Rectangle as below. 
  • The SetWidth() and SetHeight() methods are inappropriate for a Square as the width and height of the square are identical. This is the significant clue that there is a problem with the design. However, there is a way to subside this as shown in below code:
  • Lets say we have a method Test() as below and we pass reference to Square object in this method, the Square object will be corrupted because the height won't change. This is a clear violation of LSP, function Test() doesn't work with for derivatives of its' arguments. The reason for this is SetWidth() and SetHeight() are not declared as virtual in Rectangle class.
  • We can fix this easily as shown in below code. However, when the creation of a derived class causes us to make changes to the base class, it often implies that the design is faulty. Indeed, it violates the Open-Closed principle.  



  • So far it's good, lets say we have a method TestArea() as shown below. This method calls SetWidth() as well as SetHeight() members considering it is a Rectangle. The assertion works fine when an object of Rectangle class is passed but not for an object of Square class. Since, this methods takes reference of type Rectangle but cannot operate upon Square, which violates LSP. Making Square class derived from Rectangle class has broken method TestArea(), as we may have to put a conditional check to determine if the object passed is not the type of Square for assert to pass, which in-turn violated the OCP.
So what went wrong? in above example. Isn't a Square a Rectangle? Doesn't a "IS A" relationship hold between them?  No! A square might be a rectangle, but a Square object is not a Rectangle object. This is because the behaviour of a Square object is not consistence with the behaviour of a Rectangle object. It's the behaviour that software is really all about.

The LSP makes clear that in OOD the "IS A" relationship pertain to behaviour. Not intrinsic private behaviours, but extrinsic public behaviour; the behaviours clients depends upon. In order for the LSP to hold, and with it the Open-Closed principle, all derivatives must conform to the behaviour that clients expect of the base classes that they use.


There is a strong connection between the LSP and Design by Contract. Design by Contract says, that methods of a class has pre-conditions and post-conditions. The preconditions must be true in order for the method to execute. Upon completion, the method guarantees that the post-condition will be true. 

The rules for precondition and post-condition for derived class says that while redefining a method in derived class, you may only replace it's precondition by a weaker one, and its post condition by a stronger one.  In other words, when using an object through its base class interface, the user knows only the preconditions and post-conditions of the base class. Thus, derived objects must not expect such users to obey preconditions that are stronger then those required by the base class. That is, they must accept anything that the base class could accept. Also, derived classes must conform to all the post-conditions of the base. That is, their behaviours and outputs must not violate any of the constraints established for the base class. Users of the base class must not be confused by the output of the derived class.

No comments:

Post a Comment