Wednesday, September 11, 2013

SOLID: Liskov's Substitution Principle

In 1988, Barbara Liskov wrote:
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.

Uncle Bob's interpretation:
Subtypes must be substitutable for their base types.

Head First Object-Oriented Analysis and Design's interpretation:
"When you inherit from a base class, you must be able to substitute your subclass for that base class without things going terribly wrong. Otherwise, you've used inheritance incorrectly!" 

If none of those interpretation makes sense, read on.

I will try to explain this using Java. But before I explain what the principle means, let's define what subtype is. A is a subtype of B if it implements or extends B. For example, ArrayList is a subtype of List because ArrayList implements List. List is a subtype Collection because it List extends Collection. Because this relationship is transitive, ArrayList is also a subtype of Collection.



Now we know what I mean when I say subtype, let's try to understand the principle.  Let's say we have the following code is Java:
What the principle is saying is that I can rewrite the code to this:
Or this:
Even though I changed Collection to List and Set, the behavior is unchanged because List and Set are subtypes of Collection, so you can substitute the subtype with its base class. What this translate to is that I can pass a List or a Set into removeAll because the method takes a Collection.

Be careful because this is a one way street. List can be substituted Collection but Collection may not be substitutable for List because List is a child of collection.

Photo by Derick Bailey

There are two ways you can violate the Liskov Substitution Principle:

  1. When the subtype Class removes functionality from the base class.
  2. When methods that reference the base class knows about its subtype.
Both of these violations translates to wrong abstraction. For example, look at this code:
Looks good right? In most cases, it is good. However, when I run this test, it will fail. I get an UnsupportedOperationException.
Why did it fail? Because Arrays.asList() returns an AbstractList, which is an unmodifiable list. It doesn't support List.add() or List.remove(). This is a violation of the Liskov Substitution Principle because AbstractList removes functionality from List. I can't substitute AbstractList (the subtype) for List (the base class). We can fix this by making the List interface have all the unmodifable methods. Then have another interface for ModifableList.


(Note: For the sake of simplicity, I ignored that the List interface implements Collection. But you get the idea.)

This way, our method can be changed to:
This makes a lot more sense because you can only remove items in list that are modifiable.

The second way to violate this principle is having your method knowing details about the subclass.
The calculateArea violates the Liskov Substitution Principle because it knows about the subtype of Shape. This makes it hard for the method to close for modification. What happens when you add a circle, now you have to add another if statement to check if it's a circle. To fix this, we can move the calculateArea() to the Shape class. Then each Shape can implements its calculateArea() so we don't have to check to see what type it is.

This is another way you can violate the principle: This method knows the subtype of the base class. It casts the list to LinkedList, but not all lists are LinkedList. Therefore we cannot substitute the subtype ArrayList in this method even though List is ArrayList base class.

And that's Liskov Substitution Principle. If you are removing functionality, then you want to ask, does this subtype belong to this base class? Or if you find yourself downcasting like I did with the LinkedList, then you want to replace the base type with the subtype. Or if you find yourself checking for case type, then you should ask, does this method belongs in the subtype class? Doing any of these things is an indication that you may be violating the Liskov Substitution Principle.


 Resource:
  1. Software Development Principles Patterns Practices
  2. Head First Object-Oriented Analysis and Design
  3. Object Mentor - Liskov's Substitution Principle
  4. OODesign - Liskov's Substitution Principle

0 comments:

Post a Comment