Inheritance is the most common approach for code re-usability in object oriented programming. Sometimes we may need only one function to be used from a class but still inherit the entire class. This results in all the base class members, with public and protected access modifier, also getting exposed to the derived classes. Containment provides the alternative to the use of inheritance along with the ability to control which members of a class should be available to the other classes.
What is Containment ?
Containment is the simple approach, in which a class B, which needs access to the members of class A, contains an instance/object of class A, rather then inheriting the class A.
Although, controlling access to the members of a class may be the reason why we may not prefer inheritance for code re-usability, there are few points which may help us to decide whether we should go for inheritance or containment. So let’s discuss these points.
Point 1 : Do we need entire functionality of the base class or just some part of it.
Let’s take an example of a class named LivingThings, used to classify all the living things in the world, having basic functions like CanWalk(), CanTalk(), CanRun() etc. Now with these functions, it will be meaningful, if we create a class named Mankind (representing the humans) and derive it from the LivingThings. This is because, a human being can walk, talk and run. So we can inherit common functions from the LivingThings class and extra add functionality in the derived class.
However, will it make sense to have a class named Animals, and derive it from the LivingThings class. The answer is No, as, an animal can walk and run but animal cannot talk. Using inheritance will still expose this method to the derived class. So if we really want to use inheritance with this concept, it will be better, if we break the LivingThings class into further components(depending on the requirements) and further use them as base classes.
If we have such a situation, where we need to access the entire functions of the base class, in the derived class, this kind of inheritance code is in sync with the Liskov Substitution Principle of the S.O.L.I.D. principles.
Point 2 : Do we need a coupled or decoupled system ?
When we inherit from a base class to create a derived class, it increases coupling between two classes as the derived class directly uses the base class and its member functions. On the other hand, using containment, via interfaces, promotes loose coupling as we are programming against the interfaces and not the concrete classes. See the point below.
Point 3 : Do we need to change the base class at run-time
This might sound a bit complex. Let’s discuss it this way. If we are having multiple base classes, implementing a common interface, then we can switch between the multiple base classes, at run-time, based on some kind of condition. See the code below :
Here, we have an interface IBaseClass, with a method named GetText to return string value. Two base classes, BaseClassA and BaseClassB are implementing the same interface. Now a third class, named DerivedClass, is using the containment and calling the required method at run time i.e. changing the base class at run-time, based on some kind of business logic. Of-course, we could have done it using the inheritance, but then we will loose the advantage of loose coupling.
Point 4 : Do we need to control access to the public members class ?
This is perhaps the most important point to be considered for designing the application architecture i.e. which component of a class should be accessible to the other classes. Using inheritance, it will only control restriction to the private members. Protected and public members will be still accessible to the other classes. See the code below :
This can be controlled if implement containment, using the interfaces. Now see the following code, where we have the containment using the interfaces.
We have altered the code and added 2 public methods in the Base classes i.e. Method1() and Method2(). Despite being public in nature, still the two methods are not accessible by the derived class. This is because, we are using the interfaces for containment, which has only one method GetText().
Point 5 : Avoid change in client code due to base class code change ?
Consider the following code snippets. We have a situation, where we need to use a class function in another class. We have case 1, where we will use inheritance, and case 2 where we will use containment.
- Using Inheritance
Here, we have a class ClassOne with a method to return sum of 2 numbers. A derived class, ClassTwo, inherits this class and adds its own functionality in it. Client code is accessing the base class method directly, using reference of the derived class i.e. _classTwo.
- Now, using Containment, same implementation is changed to :
Here, ClassTwo is containing the ClassOne instance, and adds another method in it-self to create a wrapper, for client code to access the ClassOne method.
With these two implementations, we have following two cases :
- If we change the return type of the function ReturnSum in the base class from Int32 to Double, using inheritance will directly break the client code. Consider the same kind of change when there are multiple derived classes are using the same function. All will be affected by this change. Or the client code is using some kind of DLL for reference. It will directly break their code. But had we used cotainment, we would require our ClassTwo function to adjust the change and rest of the client code will be fine, even if it was using a DLL reference
- Now suppose, your ClassOne is a third party component and you combine it with your own ClassTwo, to generate the complete system. Here, if the third party adds a function in its ClassOne, it will be directly available to client code. But had we used containment here, it would require you, to modify your ClassTwo, and add another wrapper method in your class i.e. ClassTwo. This method will in-turn, call newly added method in base class or ClassOne. Client code can use the new method in the ClassOne, indirectly, by calling the wrapper method which you add in the ClassTwo.
By these points that we discussed above, containment might seem a better option then inheritance. But in reality, its not easy to decide which one is the better approach to be used. Its entirely on our requirements that we should decide whether to go for inheritance or containment. These are some of the common situations which we encounter in our daily routine code. The one the closest with our requirements, should be our preference. Hope you enjoyed reading this article…!!!