Written by: Mojtaba Sarooghi, Queue-it Software Developer
Recently, I have discussed the topic of composition over inheritance in my team at Queue-it. If you Google inheritance versus composition, you will find many blogs and Stack Overflow discussions around this topic.
The increases in usage of IoC containers and dependency injection patterns have made the usage of composition pattern easy these days, especially with the popularity of micro services focusing on single responsibility, small and less dependent modules. Besides, JavaScript is getting more and more popular, and the lack of native class inheritance in JavaScript helped programmers think of how easily they can live without class inheritance by trusting composition more.
People have different ideas, and comparing these two techniques via different viewpoints can lead you to find radical ideas supporting one and discouraging usage of the other one. Many ideas have particularly favored composition over inheritance, and they mention inheritance as a bad practice (Google “Inheritance is Evil”). For a detailed and balanced discussion on this topic, you can have a look at this.
For me, it has been hard to accept inheritance as an ‘evil’. If I look back at how I learned object-oriented programming, all of the sources I read always referred to inheritance and polymorphism as main concepts of OOP, alongside object, class, and encapsulation. In addition, I participated in several large, successful projects that were primarily written and based on the usage of inheritance and the template method design pattern (you could easily find more than five levels of inheritance in some classes). Recently, I have been working on some projects more reliant on IoC containers and defining loosely coupled small classes, and now I can admit that the usage of composition is easier and faster than inheritance in some cases, so it made me think about this topic more in-depth.
Some of the Problems of Using Inheritance
Coupling and Implicit Dependency Between a Super Class and its Subclasses
Inheritance causes subclasses and super classes to become coupled to each other, and it introduces implicit dependency between them (as a result, changing in a superclass might result in changes in its subclasses). This coupling practically means that when you are coding in a subclass, you might have to get some detailed knowledge of the super class (inheritance is, to some extent, breaking information hiding, and encapsulation).
Many classes might inherit from a base class, but with time, you might find more and more tasks are shared in some subclasses, though not all, and if you want to add the shared code in the base class, you will break the Liskov principle. So, you probably have to redesign your hierarchy structure, which is costly.
In the same way, when you have many shared responsibilities and you put them in your base class, but it gets too big, then you will have to break your base class and do some refactoring. Again, it might be costly because subclasses depend on the base class, so it might cause changes in subclasses as well.
Limitation in Subclasses
Programmers who want to inherit their new classes from a base class (that might be written by somebody else) have to live with the base class limitation.
Unit Tests
Although there are ways to write unit tests for classes in hierarchy, it seems that unit testing or mocking a class, which is part of inheritance chain, is harder than unit testing or mocking a simple class without any level of inheritance.
Compile-time Dependency
The dependency between a subclass and its super class is compile time, and you cannot change this relationship at runtime. It means you cannot change the parent of a subclass at runtime, in contrast to composition dependency, in which you can easily inject a new implementation of an interface at runtime.
Therefore, if we just think of inheritance as a way to reuse and share code between classes, it looks like overkill, and in many cases, inheritances could be replaced with composition to gain this reusability in a cleaner and easier way. So, it seems inheritance is a useless concept, right? Actually, this conclusion is wrong. We should notice that sharing code is just one side of inheritance, and the semantic behind inheritance (the concept of a generic/specialized relationship between subclasses and super classes) is not just for sharing code — it can provide a good code structure.
Some Benefits of Using “IS a relation” Between Classes
Cohesion and Code Structure
Although inheritance forces dependency between a super class and its subclasses, if this dependency is well designed, not only is it not harmful, it could actually be useful and make code conceptually cohesive. For example, when you see an instance of subclass, you can make the assumption that instance can be treated like its parent. For example, in a UI reporting module, there could be different report items, like ShapeItem
, LabelItem
, FiledItem
, etc. Because all of these report items are subclasses of ReportItemBase
, the report class can deal with all items in the same way (report item subclasses are conceptually coherent).
Grouping and Modularizing Classes (Big Picture, a.k.a. Architecture)
Like the previous topic, with the right usage of inheritance, you can modularize your code, classify, and arrange concepts by generalization / specialization, making your code structured and much easier to understand and follow. A well designed structure lets you explain the whole system in different levels of abstraction; in a high level, you just talk about base classes and how they are connected to each other, and in lower levels, you will go into the details and implementation of each subclass.
Applying Policies (For Example, Cross Cutting Codes or Domain Specific Logic) in Different Levels of Hierarchy
Limiting programmers while they are extending a parent class might look like a drawback of inheritance, but it is actually one powerful feature if it is used in the right way. In template method design pattern, super classes let sub classes override some part of an algorithm, and it means the parent class can do some custom actions before and after the sub class code (applying policies) without any need to let programmers who are designing the subclasses get distracted. For example, in your business class, you can do custom logic, log, encrypt or decrypt data, data compression, security validation, etc., in different levels based on the type of a domain object and before sending queries to the repository.
Framework and Plugins
Imagine that you are developing a big monolithic application and have some programmers with different levels of technical abilities. They want to add new features to the project for the purpose of costs, integrity, reusing, applying policies, etc.; you have developed a framework in which programmers can add their custom code into the project just by extending base classes. A good sample for this usage is creating a new custom UI control while doing Windows Form programming. Form controls will inherit from each other and it is easy to extend a base control class and make a new UI control. In this case, the correct class as superclass should be selected, and then the programmer will extend the base and implement just some specific functions without going into the details of how Windows controls work, which makes easier and cheaper programming style.
Compile-time Dependency
In the previous part, we referred to this static dependency as a weak point for inheritance, but personally, I sometimes see it as an advantage of inheritance as I can follow my code statically in my favorite IDE (Visual Studio) and see how the base class is used and implemented, and I can use it as documentation of my code.
In summary, I think both techniques have their own usage. Now, after spending more time on this topic, I can partially agree that depending too much on inheritance might not be a good idea for small applications. Still, I think the benefits and power of inheritance in big applications, libraries and frameworks outweigh the drawbacks. Conversely, composition is easier to use, it keeps encapsulation, defines more explicit dependencies between classes (just dependency on interfaces), and totally fits to be used with IoC, and is also easier to be unit tested. Though a big drawback of too many small classes connected by composition is that the code base would lose its cohesion, it is hard to follow code structure, and it might be hard to apply policies if you do not have a base class.
The post Composition over inheritance appeared first here.