Use composition, not inheritance


Recently I came across a bunch of code that needed some refactoring (apart from completion!). In particular, this class caught my attention.

[gist id="be59cdf4fc8ae0e47b0f"]

The first thing that should come to your mind (as it came to mine) is that, if we're not defining any additional behaviour, we shouldn't define a class, but simply use a syntax like Map<String, String> sessionContext.

But, in addition to this, something more subtle is just around the corner.

For example, after having defined such a class, somebody could think of adding some functionality, like counting the number of elements put into the map. There's seemingly nothing wrong in doing:

[gist id="2808051a328e2344d91a"]

However, this won't lead to the expected behaviour. Why? In HashMap, putAll() will call multiple times put(). Since both methods are overridden in the class, invoking putAll() will add correctly the size of the Map we passed as an input, but the put() method will increase again the counter by 1 for every element in the map.

By extending HashMap, we violated encapsulation - our class now depends on the implementation of HashMap and any change in this class is directly reflected on our class. We clearly don't want this to happen, specially if our class depends on some implementation which we are not responsible for.

The solution to this problem is to use a different approach. Instead of extending the class, we should build a wrapper on top of it (or - in other terms - decorate it). The wrapper class will contain a reference to the dependency and instead of inheriting from that dependency, it will expose the same interface, adding functionality to the existing methods that will be then called on the contained object.

[gist id="43216d95475943a66f3a"]

Not only this will make our implementation less coupled to the dependency, but we can apply it not only to HashMap, but to any other kind of Map.

The problem with the situation described before is that inheritance should be used only when we're sure that the classes have a "is-a" relationship. By inheriting a class, we inherit all potential problems in that class - and specially if we're not responsible for that class, problems may rise. Unfortunately, Java API has got some problems regarding this aspect.

(note: although I used this real-world example for describing why we should use composition over inheritance, I still think that for the problem I supposed they were trying to solve with the original class using Map<String, String> sessionContext would be better!)