The Open-Closed Principle is the heart of OO design. It advocates for abstraction to provide extensibility to any object, which is typically implemented using inheritance. Inheritance itself can lead to many possibilities, some of which can lead to wrong design. Liskov Substitution Principle (LSP) (pdf) is a guideline for inheritance design:
What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
It provides us the definition of a subtype or derived class. Programmatically, a function or method using object of a base class should behave the same when it is made to use object of a class derived from the base class. This falls inline with the notion that a subtype must confirm with the interface of its supertype. However, this is not true only syntactically. LSP tries to address the behavioral notion of subtyping.
The popular rectangle-square or ellipse-circle problems are result of violation of LSP. Lets take the example of the rectange square. By the “IS A” definition, square IS A rectangle. The behavior though can be different. The square can be considered as a special case of a rectangle where all the sides have same dimensions, and so can be derived from the rectangle. However, there can be some behavior of the rectangle that will fail with this special case. For example, when a rectangle is to be built with a certain area, there can be multiple combinations of the rectangle, but not with the square. This property of the rectangle that it can change its dimensions without changing the area cannot be addressed by the square. A functionality using this attribute of the rectangle will fail when it operates on a square.
In the real world parlance, rectangle photo frames with different dimensions can be manufactured for the same area, but it is not so with a square frame. This probably brings us to the essence of this
Square is a Rectangle, but a square object is not a rectangle object.
This stresses on the behavior of the square object rather than its structure. While only structure can be compatible, it is of importance that the behavior should also be compatible to follow the subtype-supertype relationship.
Change Inheritance Design
This does not mean that inheritance is faulty. What we realise here is that the rectangle and square have common behavior which can be modelled into a common base class. This will enable reuse of the common behavior and allow definition for specific behavior.
One thing that requires a mention here is that the above example of rectangle and square will fail if there is any functionality which is using the unequal dimension attributes of the rectangle. But, if the unequal dimension attributes are not being used, then LSP cannot be proved. However in such cases existence of the rectangle class itself can be questioned.
Design By Contract Methodology
As illustrated by Robert Martin (pdf), Design By Contract is related to LSP. The Design By Contract (DBC) defines that every software entity is obliged to continue providing service to other entities. The contract is defined by a set of preconditions and postconditions, which are programmatically translated into signature of the function/method. The caller satisfies certain preconditions upon which the callee with satisfy the postcondition. DBC says
When redefining a routine [in a derivative], you may only replace its precondition by a weaker one, and its postcondition by a stronger one.
In the rectangle-square example, the rectangle might have a method
setDimensions(height, width), which for square can be translated to
setDimensions(side). Here the square has replaced the rectangle’s precondition by a stronger one, causing violation of DBC. This can be used effectively to make sure that LSP is not violated.
Back to Design Principles.
Copyright Abhijit Nadgouda.