(Warning: I’ve just looked up “mix-in” on Wikipedia and their definition isn’t quite what I’m used to. Apologies if I’m using the wrong terminology. What I think of as a mix-in is a proxy object which is used to do a lot of the work the class doing the mixing says it does, but preferably with language/platform support.)
I’ve blogged before about my mixed feelings about inheritance. It’s very useful at times, but the penalty is usually very high, and if you’re going to write a class to be derived from, you need to think (and document) about an awful lot of things. So, how about this: we kill of inheritance, but make mix-ins really easy to write. Oh, and I’ll assume good support for closures as well, as a lot can be done with the Strategy Pattern via closures which would otherwise often be done with inheritance.
So, let’s make up some syntax, and start off with an example from the newsgroups. The poster wanted to derive from
Dictionary<K,V> and override the
Add method to do something else as well as the normal behaviour. Unfortunately, the
Add method isn’t virtual. One poster suggested hiding the
Add method with a new one – a solution I don’t like, because it’s so easy for someone to break encapsulation by using an instance as a plain
Dictionary<K,V>. I suggested re-implementing
IDictionary<K,V>, having a private instance of
Dictionary<K,V> and making each method just call the corresponding one on that, doing extra work where necessary.
Unfortunately, that’s a bit ugly, and for interfaces with lots of methods it can get terribly tedious. Instead, suppose we could do this:
Now, that’s a bit simpler. To be honest, that kind of thing would cover most of what I use inheritance for. (Memo to self: write a tool which actually finds out how often I do use inheritance, and where, rather than relying on memory and gut feelings.) The equivalent of having an abstract base class and overriding a single method would be fine, with a bit of care. The abstract class could still exist and claim to implement the interface – you just implement the “missing” method in the class which proxies all the rest of the calls.
The reason it’s important to have closures (or at least delegates with strong language support) is that sometimes you want a base class to be able to very deliberately call into the derived class, just for a few things. For those situations, delegates can be provided. It achieves the same kind of specialization as inheritance, but it makes it much clearer (in both the base class and the “derived” one) where the interactions are.
One point of interest is that without any inheritance, we lose the benefits of a single inheritance tree – unless
object becomes a general “any reference”, which is mostly what it’s used for. Of course, there are a few methods on
System.Object itself which we’d lose. Let’s look at them. (Java equivalents aren’t specified, but Java-only ones are):
ToString: Not often terribly useful unless it’s been overridden anyway
GetHashCode/Equals: Over time I’ve been considering that it may have been a mistake to make these generally available anyway; when they’re not overridden they tend to behave very differently to when they are. Wrapping the existing behaviour wouldn’t be too hard when wanted, but otherwise make people use
IEquatable<T>or the like
GetType: This is trickier. It’s clearly a pretty fundamental kind of call which the CLR will have to deal with itself – would making it a static (natively implemented) method which took an
objectargument be much worse?
MemberwiseClone: This feels “systemy” in the same way as
GetType. Could something be done such that you could only pass in “
this“? Not a terribly easy one, unless I’m missing something.
finalize(Java): This could easily be handled in a different manner, similar to how .NET does.
wait/notify/notifyAll(Java): These should never have been methods on
java.lang.Objectin the first place. .NET is a bit better with the static methods on the
Monitorclass, but we should have specific classes to use for synchronization. Anyway, that’s a matter I’ve ranted about elsewhere.
What are the performance penalties of all of this? No idea. Because we’d be using interfaces instead of concrete classes a lot of the time, there’d still be member lookup even if there aren’t any virtual methods within the classes themselves. Somehow I don’t think that performance will be the reason this idea is viewed as a non-starter!
Of course, all of this mix-in business relies on having an interface for everything you want to use polymorphically. That can be a bit of a pain, and it’s the subject of the next article in this series.
One thought on “Wacky Ideas 1: Inheritance is dead, long live mix-ins!”