About Me

Hello, I'm an eclectic software professional working with enterprise software at SAP. This blog only contains my personal views, thoughts and opinions. It is not endorsed by SAP nor does it constitute any official communication of SAP.

Wednesday, August 17, 2011

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.

2 comments:

  1. Your idea is very similar to this: http://www.cs.cmu.edu/~donna/public/malayeri.TR08-169.pdf

    It uses two types of "inheritance".
    One is like your wraps and other is like extends.

    ReplyDelete
  2. @NN: Fascinating read! Thanks for the link...

    ReplyDelete