In the last blog, we discussed how using inheritance, even though a useful tool, can become problematic when used without precautions. To counter the violation of abstraction, we can consider using Composition. Which brings us to the obvious question, what is composition?
Instead of extending the class, we could create a private reference of the Set mentioned in the aforementioned example. This leads to multiple advantages. Let’s take a look.
As you can see, InstrumentedSet class contains an instance field of the type Set. On examining the implementation of methods add( ) and addAll( ), you can see that our code is totally detached from how these methods have been implemented internally. Each instance method here calls the same method on the contained Set instance. This process is known as forwarding. Any change in the super-class can not break our expected behavior. Even adding new methods to the super-class makes no impact in our code. That is a major advantage when compared to the inheritance-based approach.
Here, we can see the implementation split into two parts. The set itself and the forwarding class which does the whole forwarding logic. Unlike the inheritance-based approach, this code will produce the value 2!
The instrumented set here takes a set as a constructor argument and adds its instrumentation ( in this case, the ability to count all inserted elements ever). It also implements a Set interface, therefore, the InstrumentedSet takes one set and converts it into another.
Advantages of Composition
- Inheritance based approach required constructors for each version of constructors supported by the superclass. Unlike that, our InstrumentedSet constructor works in conjunction with every pre-existing constructor.
- In our earlier approach, we could only model for one concrete type of Set at a time whereas now we are loosely coupled with the Set interface.
InstrumentedSet<String> set = new InstrumentedSet(new TreeSet<String>()); InstrumentedSet<String> set = new InstrumentedSet(new HashSet<String>());
- Similarly, it can convert a pre-existing set to an InstrumentedSet.
Disadvantages of Composition
There is just one major disadvantage of working with Composition. They don’t suit well with callback frameworks. Suppose our wrapped set (A) passes its reference to another object (B) in a function using the “this” keyword. In our wrapper class, when we call the same function, we expose access to A instead of the wrapper instance to B. It is known as the self problem.
Although one could say we need to write multiple forwarding methods in the ForwardingSet class, that doesn’t amount to much of a disadvantage since it’s only done once for Set and can incorporate any implementation later.
Inheritance is powerful but needs to be used cautiously. Always ask yourself before using inheritance whether the classes form a definite “is-a” relationship and is the superclass well-written or will it propagate any flaws? Even in satisfactory conditions, avoid inheritance outside of the package or if the superclass isn’t designed for an extension as it may lead to fragile code in the long run.
I hope this blog was able to help with deciding your code structure. Feel free to comment your questions below and I will try to address them. This blog is just a piece out of Effective Java by Joshua Bloch so for more information please refer to the book. For more such blogs, stay tuned!