nosewheelie

Technology, mountain biking, politics & music.

The practical problems of inheritance

without comments

At the day job, we’ve been working with a well known open source project in order to simplify our interface to some infrastructure (that our code must use to interact with vendor code). The guys who run the project have been really good, and we’ve been speaking to them on a regular basis, giving (and taking) - amongst other things - advice on how the project can be improved. Today’s conversation centred around their testing infrastructure, where we hit upon a thorny subject that crops up quite a lot - deep inheritance hierarchies.

Our problem is that we have our own JUnit testing infrastructure (i.e. inheritance hierarchy) and the open source project has one too, and as we can only inherit from one parent in Java we’re stuck. The issue comes about as the test classes that we need to use cannot be delegated to; they make the assumption that they are the test being run (which is important to them), which in our case isn’t true (they’d be a field in our class). One option of course is to inherit from them, and delegate to our classes. This however highlights several common problem with locking code up using inheritance.

  • Using the hierarchy binds you to all the parents of your immediate super class, even if they offer functionality that you don’ want.
  • The general testing infrastructure is tied to single implementation - JUnit, and hence cannot be used with other frameworks such as TestNG or Instinct.
  • Subclasses can inadvertently break the functionality of their parent, say by implementing runBare() and not calling super.runBare(). As JUnit has quite a few extension points so without perseverance it isn’t easy to know which one to override in what circumstances.
  • Inheriting can increase your exposure to a framework. So for example, if you dump all of you code into a TestCase subclass, your interface to JUnit is quite thick. You can easily get around this by making the subclasses as thin as possible, and quickly delegating out to your real classes. In this way the subclass is only there as an interface to the framework, and you can re-use your real code elsewhere. In the case of open source projects this is even more important, as your clients can reuse your code as well.
  • All the functionality is locked up in the superclasses, reducing your ability to use it outside of the hierarchy.
  • Logically separating behaviour in superclasses limits the subclasses ability to decide what behaviour it want to inherit, and how. In our example, the superclasses perform certain functions, in a certain order, which the inheritance gives you for free. However, as a subclass you cannot decide if, or in what order you want to use the behaviour.
  • But perhaps the most important reason is that all the benefits of inheritance can be achieved by other means; delegation, composition, static imports, decorators, etc.

All of these reasons contribute to testing frameworks such as JUnit moving away from concrete inheritance to other mechanisms. With JUnit, it’s a RunWith annotation that allows you to specify the runner to use to run the test. This is a much better way to interface with the framework, as it provides you with a limited interface to the framework (reducing clutter in your code) and provides the framework with the flexibility to be extended beyond its original design.

Written by Tom Adams

March 1st, 2007 at 9:16 pm

Posted in Java, TDD

Leave a Reply