Composition vs. Inheritance in Java.
When Introduced to Object Orientation I was very excited about inheritance and class hierarchies and what I could do with them. Over time, I've come to focus more on interfaces and (in general) use composition rather than inheritance.
Inheritance in java is super easy. If we want to build, for instance, a case-insensitive HashMap how would we do it? (I chose HashMap because it shows the good, the bad, and the ugly of each approach, while remaining trivial). With Inheritance it's pretty easy. We simply extend HashMap and override the methods dealing with keys:
import java.util.HashMap;
public class NoCaseHashMap extends HashMap {
private String toKey(Object key) {
if (key == null) {
return null;
}
return key.toString().toLowerCase();
}
public Object get(Object key) {
return super.get(toKey(key));
}
public Object put(Object key, Object value) {
return super.put(toKey(key), value);
}
public boolean containsKey(Object key) {
return super.containsKey(toKey(key));
}
public Object remove(Object key) {
return super.remove(toKey(key));
}
}
Ta-da! Done.
But, the first downside to this approach is that, in Java, we may only inherit implementation (extend) from one superclass. There are times when that seems constrictive. In this simple case that is no big deal.
The less obvious downside of Inheritance is that we've locked this into a particular Map implementation. What happens when we want a LinkedHashMap or a WeakHashMap or even a TreeMap? Early binding to a particular implementation is Inheritance's biggest shortcoming.
Enter Composition.
With Composition we can late bind to the implementation. Rather than extend HashMap, we could, for instance, take a Map object as a construction parameter and leave the implementation choice open.
The first obvious down-side to Composition is that it is not as light-weight in Java. Every method must be added, not just the ones that change.
The second, less obvious, downside is that with inheritance super classes can call the methods of the sub-class, but this is not so with composition.
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class NoCaseMap implements Map {
private Map map;
public NoCaseMap(Map map) {
this.map = map;
}
private String toKey(Object key) {
if (key == null) {
return null;
}
return key.toString().toLowerCase();
}
public Object get(Object key) {
return map.get(toKey(key));
}
public Object put(Object key, Object value) {
return map.put(toKey(key), value);
}
public boolean containsKey(Object key) {
return map.containsKey(toKey(key));
}
public Object remove(Object key) {
return map.remove(toKey(key));
}
/* Note the need to implement putAll(map) with composition */
public void putAll(Map t) {
for (Iterator it = t.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
put(entry.getKey(), entry.getValue());
}
}
// --------------------------------------
// The rest of these are just the standard cruft of composition:
// --------------------------------------
public void clear() {
map.clear();
}
public boolean containsValue(Object value) {
return map.containsValue(value);
}
public Set entrySet() {
return map.entrySet();
}
public boolean isEmpty() {
return map.isEmpty();
}
public Set keySet() {
return map.keySet();
}
public int size() {
return map.size();
}
public Collection values() {
return map.values();
}
}
More code (even if it was auto-generated) obviously, but also plug-able and flexible.
When to use Inheritance and when to use Composition?
Traditionally, we are told to use inhertance when one class has an "is a" relationship with another, and composition when one class has a "has a" relationship with another. While I do not dispute this in the slightest, I will warn that this is trickier than it sounds. We've pobably all seen the bad textbook example of something like "Programmer" and "Manager" as extending "Employee" ... well, maybe ... but perhaps it is more useful to think of "Programmer" as a set of duties that an "Employee" may have, and this same employee may later change duties for "Manager" duties. Often "is a" and "has a" relationships are more arbitrary than they might first appear. If "is a" and "has a" relationsips are just a matter of perspective, what should we do?
As a rule of thumb:
* Use composition (not inheritance) to get code re-use and/or polymorphism. as Bill Venners suggests (JavaWorld.com, 11/01/98)
* Use inheritance when super-classes must significantly interact with their sub-classes. (But consider Strategy pattern instead.)
* Use inheritance when we are forced to.
If the "parent" must call back to "child" methods, then Inheritance is usually the only good answer. However, in general, the late-binding aspect of Composition tends to be better, even with the added irritation of the the pass-through methods. What are the relationships between the classes? Is one the abstract "brains" and the other provided a strategy for some internal functionality? Or is one a "Decorator" of another where composition is obvious? Like:
new BASE64EncoderStream(new BufferedOutputStream(new FileOutputStream(file)))
I used to see problems mostly in terms of super and sub-classes. These days most problems look better solved with composition. And if it weren't for the heavier weight of composition, I'd probably use it nearly exclusively, except for when inheritance was the really obvious winner.
If intermediate classes get introduced into the heirarchy that are hard to name, it may be a good idea to consider composition; if the names don't make a lot of sense, that's probably a hint that we should be thinking harder about our design, anyway. Usually I see this happening as we try to push common code into some super-class, but that superclass doesn't actually represent any particular general concept.
There are also times when we are forced to use inheritance becuase some method expects a concrete class as a parameter, not an interface. The core java language is full of examples where implementations are expected which could be interfaces: java.io.Writer and java.io.File jump to mind.
When I see that I am forcing extenders to use inheritance, I take this as a sign that I should think hard about the architecture; in general it has turned out to be symptom of inferior design. As a rule of thumb, I try to design for interfaces and plug-ability, not sub-classing.
There are many ways of re-thinking towards plugability. Sometimes it is just a matter of using interfaces and having a default implementation. Sometimes it is a matter of thinking about the problem from the other direction: for instance, rather than having an abstract super-class which expects a concrete sub-class to implement a function or two, we can create a concrete class which expects a collaborator object to implement those functions (the "Strategy" pattern ). With understanding of just the "Strategy" and "Decorator" patterns it becomes possible to imagine completely designing for plugability without using inheritance at all.
When in doubt, I try to let testing be my guide, because testing tends to be the first place where I find myself wanting plug-ability. For instance, if a "base class" requires a lot of set-up and tear-down to instantiate and test, and a "sub-class" is adding something which doesn't directly interact with the elements that require set-up and tear-down, this may be a hint that we could restructure to use composition and test the add-on behavior in isolation more easily.
Ask Google
Custom Search
Monday, October 17, 2011
Subscribe to:
Posts (Atom)