Why is composition harder than inheritance?
(just some random thoughts...)
Let's take this Java example:
class Base {
public void method() {}
}
class Derived extends Base {}
See how easy it is for Derived
to "reuse" code from Base
? Derived
is a subclass of Base
, and in its constructor it calls Base
's constructor, constructing a Base
before constructing a Derived
:
class Derived extends Base {
// this is what gets generated by the compiler:
public Derived() { super(); }
// this doesn't exist, but illustrates what happens when we call Derived.method:
public void method() { super.method(); }
}
Also, Derived
instances have a "forwarding" method, that, when called, will delegate to the corresponding base method (this is not technically what happens, but I'm looking at the "concept" here). Isn't that convenient?
Why isn't the same capability available to compose classes? Composition is always a better strategy than inheritance in "reuse" scenarios. To compose, the delegating methods have to be manually written:
class Composed {
private Base _base;
public Composed(Base base) { _base = base; }
public void method() { _base.method(); }
}
Conceptually, Composed
looks a lot like Derived
. The differences are obvious to OO-mongers. Inheritance is an is-a (better still, a behaves-like) relationship, and Derived
instances can be subtituted whenever Base
is needed. Composition is a has-a relationship, and instances of Composed
cannot stand in for Base
. Composition is more flexible because changes in Base
do not force changes in Composed
's interface, and we can choose which methods of Base
to expose through Composed
.
There's a degenerate version of composition called a wrapper, that wraps a class, exposes all its public methods as its own, and delegates all calls (and it can also adjusts some calls, like a proxy). But, as the Composed
example shows, it's painful to write compared to the ease of inheritance. So it's not by chance that people use inheritance for the wrong reasons, it's convenience.
What if we had a wrapping "operator", much like the inheritance one?
class Wrapper wraps Base {}
Compare this to:
class Derived extends Base {}
The following constructor would be generated by the compiler (note that it takes a parameter):
class Wrapper wraps Base {
// "wrap", like "super", does compiler magic:
public Wrapper(Base wrappedBase) { wrap(wrappedBase); }
}
Wouldn't that bring composition (rather, wrapping) to the same level as inheritance in terms of language support? Would this be worthwhile? I think it would, and it also makes sense to allow multiple-wrapping (as opposed to multiple-inheritance), with some kind of conflict resolution strategy.
Should we be able to substitute a Wrapper
for a Base
? Maybe. This looks like a stronger form of duck typing, one that's actually verifiable by the compiler. Only within a class derived from Base
(or in Base
itself) we would be in trouble, if it accesses private or protected members of other instances of the same type. Maybe then a class should only be allowed to access its own private or protected members, and not the ones of other instances, because we don't know whether those instances will be wrappers or not. Scala has this kind of access control (by marking a member as private[this]
or protected[this]
). We could also declare any interfaces that Base
implements in Wrapper
, and work with these interfaces. And, wrappers shouldn't break the semantic contract of the wrapped class (and thus adhere to the Liskov Substitution Principle - pdf).
From another angle, traits try to solve exactly the same problem, by creating a new language-level construct (the trait, duh) that composes nicely into classes. Scala has traits. Maybe then, wrappers could be used to work with old code, and new code should be designed with traits.
Your idea is very similar to this: http://www.cs.cmu.edu/~donna/public/malayeri.TR08-169.pdf
ReplyDeleteIt uses two types of "inheritance".
One is like your wraps and other is like extends.
@NN: Fascinating read! Thanks for the link...
ReplyDeleteAppreeciate this blog post
ReplyDelete