In software development, organizing our codebase in a way that is easy to maintain is always a good practice, and this idea is not new. Actually, I think this started in 1968 when the term software crisis was coined. Since then, we have developed many principles to help us achieve this goal. One person who has made many contributions to this field is Barbara Liskov. She is very famous for the Liskov Substitution Principle (LSP), but she also invented the Abstract Data Type(ADT), which is the core concept of classes and object-oriented programming. Here, I want to discuss composition compared to inheritance and how understanding the Liskov Substitution Principle can help you develop better software.
First of all, currently, it is already a common practice and advice to prefer composition over inheritance. However, this was not always true. At least not for me; when I learned about object-oriented programming in earlier 2002, I was told that inheritance was the best approach to reuse code. I was an inexperienced software engineer at that moment, and it took me a long time to understand the inheritance issues. Barbara Liskov talked about her preference for using composition over inheritance long ago, in the late 60s or early 70s. This reminds us about the importance of learning the foundation well and reading the past to understand how we arrived at the present.
However what is precisely the problems with inheritance? As Barbara Liskov said, the problem is that inheritance is used for two completely different ideas: implementation and type hierarchy. She mentioned that the implementation is the bad part of inheritance while the type hierarchy is the good one.
One problem with the implementation is that it usually breaks the concept of encapsulation: When we have a sub-class that uses direct variables of a super-class, we are breaking the encapsulation. The problem here is that we may need to modify the sub-class when we change the super-class. Another issue is that we can make a sub-class from a super-class when they are not directly related. Then, we can use composition over inheritance to avoid these problems, and Barbara Liskov mentioned exactly this point. Therefore, using composition over inheritance is also an excellent and old idea by Barbara Liskov.
The type hierarchy is good because it allows us to write a piece of software that could be replaced without problems. Currently, we have ways to implement type hierarchy that try to mitigate the problems of implementation she mentioned. In Java, we have the concept of Interfaces; in other languages, such as C++ and Python, we have abstract classes, for example. However, in the end, it is the developer’s responsibility to use these concepts correctly. We can use Interfaces and abstract classes wrongly too.
These two concepts, implementation and type hierarchy, are not easy to see as different concepts because we were usually taught they are the same; when it is clear, it is not the truth. So, this is the importance of the Liskov Substitution Principle and understanding it well. I’m quoting below what she originally said:
"Objects of subtypes should behave like those of supertypes if used via supertype methods"
Barbara Liskov
As she said, It is not enough to have only the syntax; we should have semantics too. A wrong example of applying LSP she mentioned was a paper that made a stack and a queue data structure a subtype of each other by naming the methods generically enough. Here, the syntax is correct when we use one type or another in our program; however, semantically, it is wrong. Stack is a Last in First out(LIFO) data structure, while queue is a First in First out(FIFO) data structure. This could create many problems in our software because one’s behaviour is different from the others.
A good example of applying LSP is a different implementation of the same concept. For example, a Binary Search Tree(BST) and a Balanced Binary Search Tree(BBST). Both are syntactically and semantically identical to our program. The difference is only the performance in the implementation. While the BST has a linear time complexity in the worst case, the BBST has a logarithmic time complexity. Then, we can use either a BST or a BBST in our program without any problem. This is a good example of sub-types and the Liskov Substitution Principle.
If you want to learn more about Barbara Liskov, her work on Abstract data types, and her principle, I recommend watching her speech during her ACM Turing Award acceptance, entitled “The Power of Abstraction.” You can find the slides she used here. In my opinion, this is a must-watch talk for all software engineers. Barbara Liskov described how she invented the concept of Abstract Data Type and the Liskov Substitution Principle. She also showed the history of software development and object-oriented programming in this talk. It is a brilliant talk!
You can also read the original paper where she created the LSP to go deeper.
If you are starting to learn object-oriented programming, choose composition over inheritance. When you use inheritance, always pay attention to whether you are breaking the concept of encapsulation; if you are, it is time to check your decisions.