30May
Liskov Substitution Principle

The implementation looks ok. Birds usually eat and fly. In the distinctive case of an Ostrich we do not have the flying capability so an exception is thrown. However, the exception is unexpected for the code inside main merthod, so the program crashes. The client code that uses this hierarchy has no indication that fly( ) method will have an inconsistent behaviour between different subtypes of Bird class. For that matter it will not be prepared for an exception to be handled.

How can this be fixed? One easy way is to be prepared for such an exception. Put a try{ } .. catch( ) block around fly( ) calls and catch any exception (assuming those exceptions are documented) and handles them accordingly. But this is still inconsistent, because the exception is not thrown due to a particular execution flow in the method, which might appear on any other fly( ) implementations; it is because there is particular fly( ) implementation,which belongs to a particular sub-type, and for that matter the definition of the fly( ) method from the super-class was enhanced with the thrown exception. This is still a violation of LSP, because the sub-types are not completely substitutable for the super class since not all of them will ever throw that exception.

The correct way is clearly separate the two hierarchies: the one for flying birds and the one for non-flying birds.

The previous article has discussed about the Open-Closed Principle(OCP) and it is the foundation for building maintainable and reusable code. The primary mechanisms behind OCP are abstraction and polymorphism, and most of the programming languages support these through inheritance.

The questions raised here are what design rules should govern the use of inheritance, what are the traps that will cause creation of hierarchies that do not conform OCP. This article is trying to answer these questions.

Liskov Substitution Principle (LSP) is the third principle in S.O.L.I.D. set of principles (hence the letter L) and addresses some of these issues. In essence, the principle is stating that Methods that use references to the base classes must be able to use the objects of the derived classes without knowing it. It means, the sub-types must be replaceable for super-types without breaking the program execution. LSP is closely related to OCP in the sense that violation of LSP in turn violates the OCP. In other words, if sub-types are not replaceable for the super-type reference, then in order to support the subtype instances as well, changes to the existing code must be made, which is a clear violation of OCP.

This can be better seen through an example.

public class Birds {
public static void main(String[] args) {
List birds = new ArrayList( );
birds.add( new Pigeon( ));
birds.add( new Ostrich( ));
makeBirdsFly( birds );
}

public static void makeBirdsFly( List birds ) {
for ( Bird bird : birds ) {
bird.fly( );
}
}
}

class Bird {
public void fly() { }
public void eat() { }
}

class Pigeon extends Bird { }

class Ostrich extends Bird {
public void fly() {
throw new UnsupportedOperationException();
}

The implementation looks ok. Birds usually eat and fly. In the distinctive case of an Ostrich we do not have the flying capability so an exception is thrown. However, the exception is unexpected for the code inside main merthod, so the program crashes. The client code that uses this hierarchy has no indication that fly( ) method will have an inconsistent behaviour between different subtypes of Bird class. For that matter it will not be prepared for an exception to be handled.

How can this be fixed? One easy way is to be prepared for such an exception. Put a try{ } .. catch( ) block around fly( ) calls and catch any exception (assuming those exceptions are documented) and handles them accordingly. But this is still inconsistent, because the exception is not thrown due to a particular execution flow in the method, which might appear on any other fly( ) implementations; it is because there is particular fly( ) implementation,which belongs to a particular sub-type, and for that matter the definition of the fly( ) method from the super-class was enhanced with the thrown exception. This is still a violation of LSP, because the sub-types are not completely substitutable for the super class since not all of them will ever throw that exception.

The correct way is clearly separate the two hierarchies: the one for flying birds and the one for non-flying birds.

class Bird {
public void eat() { }
}

class NonflyingBird extends Bird { }

class FlyingBird extends Bird {
public void fly() { }
}

In this way, any client code that uses this hierarchy, will know exactly what to expect when dealing with super-classes, even though the actual instances will be of sub-classes. In the case of initial example, the root class of the hierarchy will not have fly( ) method so all sub-classes will have a consistent behaviour with that is expected by the client code when is dealing with Bird objects. If we still want to make birds fly, we need to use a hierarchy of classes basedo n FlyingBird class.

The contract between the class and the client code is very clear. That is why this principle is also known as Design by Contract.

What is important about LSP is that it cannot be evaluated in isolation. As can be seen from the initial example, the hierarchy was ok designed, but that only in isolation, not in relation with a client code that uses it. So, any LSP evaluation should always be done considering how the clients of the class hierarchy are going to use it.