".. inheritance definitely seems to be an "unsolved problem" in OO languages. As already mentioned, it involves so many competing forces: typing, interface vs. implementation, single vs. multiple, etc. As a result, languages have typically picked some workable compromise, and lived with the limitations this produces."
And, so has Java. In Java, the recommended practice is to make heavy use of interfaces and design concrete classes using interface inheritance. But there is no elegant way to reuse interface implementations, unless you resort to tools like AOP that works on byte code weaving. People often try to use public inheritance on concrete classes in Java to add new behaviors or override existing ones and lands up losing extensibility in orthogonal directions.
Consider the abstraction
java.util.Map<K,V>
. The following are the various collaborations that the abstraction participates in.- It has subinterfaces like
SortedMap<K,V>
,ConcurrentMap<K,V>
, which implies linear extension of the contract. That is, these subinterfaces add additional behavioral contracts (no implementation) to the generic map. - It has multiple implementations like
HashMap<K,V>, TreeMap<K,V>
etc.
Hence any added behavior to a generic component like
Map<K,V>
needs to work across all extension points of the abstraction. This is where the suggested implementations by the original authors of SafeHashMap<K,V>
falls flat. As Eugene points out, extending java.util.HashMap<K,V>
will result in the additional behavior NOT being implemented in other variants of Map<K,V>
. While adopting a wrapper based implementation will not scale with SortedMap<K,V>
and ConcurrentMap<K,V>
.And, of course, implementing the contract of Map<K,V< ground up with additional behavior will force a blatant copy-paste based implementation.
Eugene's implementation, based on static methods and camoflaged with static imports gives it the best possible shape in Java.
Can we do better ?
No, not in Java, unless you use tools like aspects, which may seem too magical to many, and of course, is never a substitute to better language features.
What we need is the ability to compose independent granular abstractions that can seamlessly mix in the main abstraction additively to introduce new behavior or override existing ones. CLOS refers to this technique as mixin based programming - Gilad Bracha, in his OOPSLA 90 paper defines a mixin as an abstract subclass that may be used to specialize the behavior of a variety of parent classes. It often does this by defining new methods that perform some actions and then calls the corresponding parent methods.
The example in CLOS, which the paper illustrates is ..
(defclass Graduate-mixin () (degree))
(defmethod display ((self Graduate-mixin))
(call-next-method)
(display (slot-value self 'degree)))
In the above example, the mixin method
display()
invokes call-next-method
despite the fact that the mixin does not have any explicit parent. The parent comes in implicitly when the mixin class is mixed-in with another abstraction that supports the same method display()
. (defclass Graduate (Graduate-mixin Person)())
Here
Person
is the superclass of Graduate
and Graduate-mixin
mixes in with the hierarchy to provide the additional behavior. The mixin has no independent existence and cannot be instantiated on its own. It comes to life only when it is mixed in with an existing hierarchy. Hence the term abstract subclass.Mixins provide a notion of compositional inheritance that allow sharing of implementations along with pure interface inheritance, without the noise of an intermediate abstract class - it is like being able to provide implementation to Java interfaces. There have been a few proposals in the recent past for adding mixins in Java.
Till we have mixins in Java 7 ..
Scala provides mixin implementations as traits. Reusable behaviors can be modeled as traits in Scala and mixed in with concrete classes during class definitions as well as object creation. The most commonly demonstrated use of a mixin in Scala is the use the
Ordered
trait, which implements total ordering of data for the class with which it is mixed in. class Person(val lastName: String, val firstName: String)
extends Ordered[Person] {
def compare(that: Person): Int = {
//.. implementation
}
//..
}
The parent class has to implement the
compare
method, but gets convenient comparison operators for free by mixing in with the trait. This is implementation inheritance without the abstract class. And the class designer can stack in multiple behaviors simultaneously by mixing in with many traits at the same time.Scala traits also offer implementation inheritance also on an object basis, through mixins during object creation.
Consider adding a synchronized
get
method to Scala Map
s as an additional behavior overriding the existing api. We can define a trait for this behavior ..trait SynchronizedGet[A,B] extends Map[A, B] {
abstract override def get(key: A): Option[B] = synchronized {
super.get(key)
}
}
which overrides the
get
method of Map
with a synchronized
variant.Now we can mix in this behavior with any variant of a Scala
Map
, irrespective of the underlying implementation ..// Scala HashMap
val names = new HashMap[String, List[String]]
with SynchronizedGet[String, List[String]]
//.. use names
// wrapper for Java Map
val stuff = new scala.collection.jcl.LinkedHashMap[String, List[String]]
do SynchronizedGet[String, List[String]]
//.. use stuff
In fact Scala provides a complete trait
SynchronizedMap[A,B]
that synchronizes the Map
function of the class into which it is mixed in.Mixins provide the flexibility to add behaviors to existing abstractions through implementation inheritance without some of the drawbacks of existing mainstream languages as discussed above. Unlike CLOS mixins, Scala mixins do not support chaining of methods without a context. The above mixin
SynchronizedGet
needs to inherit from Map
in order to have invocation of super
in the overridden method. This takes away some of the flexibility and makes the mixin reusable only in the context of the abstraction that it inherits from. However, like CLOS, Scala mixins are also based on the linearization technique that allows users the flexibility to manipulate the order in which the behavior chains get invoked. Overall, I think mixins are a useful feature to have in your language and mixins in Scala offer a better way to use implementation inheritance for code reuse than Java.