Make your Android application rock SOLID — Liskov Substitution
Let’s dig a little bit into the Liskov Substitution Principle and how this can be very harmful to your project when you do not take it seriously.
Before starting to put some lights over the LSP, let’s make a little recap about what is it all about.
The Liskov Substitution Principle is part of the SOLID principles — the L to be precise — , which means:
Single Responsibility Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
A full explanation of the SOLID principles could be found in the first section in the SRP article.
Yeah, I know. You already hear about this principle a lot of times before today, and what it means. Probably you ask some people for an explanation and you got several answers, or maybe, instead of an answer… a meaning.
“The Liskov Substitution Principle is about inheritance”
Well… yeah, something about inheritance is… but it is not just about it. Let's try with another one:
“The Liskov Substitution Principle tells you that you need to be able to exchange objects from the same ‘family’ “
Well… this is also true, but it is not complete yet.
Remembering what we said in the first chapter of this series, in the introduction section — which I recommend to read if you hadn't yet — is
" if we are building a system with interchangeable parts, those parts must agree to a contract that allows them to be substituted for another."
Well, this last definition likes me a little bit more than previous ones. Basically, what it says is that if there is a contract (an interface, an abstract class with template methods) and you also have several objects which must agree to that contract, wherever you use one of that objects, the expected behavior must be… expected.
What about an example to clarify this? Let's do it.
Java's List Interface and Collections' sort
One of the most annoying cases of Liskov Substitution fractures is the sort operation into the Collections class, which of course I've found by receiving an unexpected (?) error in an application I was building.
In order to sort a collection in Java, you can use the sort helper method in the Collections class which takes an instance of a List<T> and a Comparator instance ( Kotlin makes this easiest with its sortBy() ) :
This means you can pass any object that implements the List<T> interface, where T is the concrete Type of the objects into the list. Imagine you have in your Android application a custom Array and you need to convert it to a List, you probably will do something like this:
Everything fine! You create your list of Strings, try to sort them and Baam! Crash!
UnsupportedOperationException was thrown
At first, you probably won't see the problem here. It should not be any. But, digging a little bit in the documentation of both, Arrays.asList() and Collections.sort(), you could find that sort operation can throw this exception if the List<T> provided does not support the add operation and Arrays.asList() returns a "fixed-size list backed by the specified array".
The returned list by the asList() method violates the Liskov Substitution Principle due to the List<T> is not replaceable in the context of the sort operation.
Collections.sort(…) expect ANY instance of a List<T> and we provided an instance of it which does not fully support the List<T>'s protocol. Of course, it implements the protocol but the add operation is canceled with an exception thrown in the method call.
The case we saw it's just one of the many examples we can find around the java code, and probably in many other languages. In this case, we "have nothing to do with it" because it's not on own responsibility. However, it's important to avoid this kind of class hierarchy constructions in our applications in order to avoid unexpected behavior in runtime and poor design.
Note that, despite the LSP talks about hierarchy, the example above is not about hierarchy but protocols instead. The important thing is, whether you are using an Interface or an abstract class, you are defining a contract and that contract must be agreed by the current classes which implement it or by the future ones.
Stay tuned for the next post of this series (The Interface Segregation Principle), and sorry for the delay in the current one.
In the meantime, you can follow me on Twitter at @nogueirasjn and, as I always say, if you found this post useful or interesting to you, please support with a clap 👏 and sharing it.