How many times did you want to test a class which inherits another class? Probably quite often. And how often did some property or method from the base class your life pretty difficult because you couldn't test the actual system under test? I guess quite often as well.
Inheritance is not bad in general but sometimes in the object-oriented paradigm, you'll find big hierarchy structures which make testing almost impossible.
Of course, good software architecture and design avoid those scenarios. Today I want to look at one best practice, called "favor composition over inheritance" which untangles those hierarchy structures in your application. For that I want to discuss the following points:
In the object-oriented programming paradigm, we often need inheritance for reusing parts of a class or extend its functionality. The best-case scenario is that we follow the Open/Closed principle. A class is open for extension but closed for modification. So the original class keeps its initial behavior.
This is generally a good thing and if we build good inheritance structures we might avoid code duplication. But on the other side, as you might already guess, if the inheritance structure gets too complicated it is very difficult to maintain and test those structures. In the end, you might end up at something like this:
This is just exemplary but you might have already found something like this in a project you worked on. Of course, it is very tempting to inherit from a parent class to extend its functionality because it is easy to do. But imagine you want to test a method inside the class CaptainHero. It is possible to do but your child's class has a tight coupling to the parent class. You simply don't know about any side effects when calling a method inside the CaptainHero class.
Also, what happens to the child classes if you want to substitute the SuperStrongHero class? Due to the tight coupling, it is impossible to switch different implementations.
Inheritance has another big impact on encapsulation. As it is put in the book Design Patterns: Elements of Reusable Object-Oriented Software (1994)
Because inheritance exposes a subclass to details of its parent's implementation, it's often said that "inheritance breaks encapsulation"
Even though, inheritance seems easy to implement and a good way to extend the functionality of a class, you now know that it is not the holy grail. But is composition a real alternative? And what does "favor composition over inheritance" even mean? Let's get another step further towards more maintainable and testable code.
In essence, "favor composition over inheritance" (FCoI) means that one class uses another class by providing a defined interface. One class is the composition of one or more classes by injecting their interfaces. FCoI is one of many best practices to provide clean code in your application.
Because a class only implements interfaces, there is looser coupling between classes and we can substitute their implementations more easily. This improves the overall maintainability and testability of your class. When writing unit tests you can easily mock dependencies and your tests focus only on what needs to be tested.
Let's see how the above inheritance structure would look like when using composition
We now use interfaces that different classes are composed of. Notice we are using inheritance at some points of our structure. As already mentioned. There is nothing bad with inheritance in general. In some cases, it can be very useful. But we need to keep it at a minimum level.
How to implement this best practice with .NET?
Let's see how our Superman class would look like:
As you can see, the Superman class is a composition of a StrongHero and FastHero. These dependencies are injected via constructor injection. We only use their interfaces. Therefore, if we need to change the underlying implementation of StrongHero or FastHero, it does not have any implication on the Superman class.
Additionally, if we need to unit test our business logic we can use the power of constructor injection and inject mocks or fakes instead of real objects.
As already mentioned when you want to write unit tests, you only want to test the system under test. When using composition you can mock unnecessary dependencies and focus only on the method you want to test.
Looking at the Superman example, we can now inject mocks of IStrongHero and IFastHero. When writing tests for the SuperHit method, for example, we now call the Hit method from the injected mock. We don't need to worry about the implementation of the Hit method and we now only focus on the business logic of the SuperHit method.
Inheritance in software development is not bad but you should look at the context you are currently operating in. Blindly using inheritance leads to very complex structures which are impossible to maintain. Being too extreme when using composition, on the other side, is not beneficial either. You can get very complex structures with composition.
Looking from the testability angle, the composition provides higher flexibility and maintainability. We can mock certain dependencies when writing unit tests. Loose coupling gives us also the possibility to change the implementation details of dependencies without the need to change anything inside the class which implements the dependency.
What do you think about inheritance or composition? How do you use it in your development process? Let me know in the comments!
A Testing API can help you decrease the structural coupling between your production code and your test code. In the long run, it will save you much time! A bold claim, of course, and it sounds very theoretical. To provide you with more depth than just a basic concept, I want to show you today how you can implement a Testing API with .NET.
Structural coupling makes refactoring our code very difficult and it costs us a lot of time and money! But there is one solution: a Testing API. It's an API with superpowers which helps you to get rid of this structural coupling. At the same time you are still able to verify your business rules. This can be a real time-saver!