Yes, novices subclass too often; they have learned about inheritance and, having overcome the obstacles, want to use it everywhere. But that’s not what we’re talking about here. Are subclasses a code smell?
I think that’s a mistake. I use (shallow) inheritance all over Tinderbox, and it’s essential.
Here’s one example, the Get Info popover in Tinderbox Six. It gives you information about the selected note.
At the left, we have a source list of various types of information we might find about a note. “Book” has the ISBN, the publisher, and tools for getting more information from libraries and other Web services. “Map” takes the address or the latitude and longitude and shows it to you on a map. “Similar” finds notes in this document with similar text.
Each of these panes is managed by its own class of view controller. We have TbxBookInfoController for books, and TbxMapInfoController for maps. The BookInfo controller knows how to talks to GoodReads and it knows how to validate ISBNs; that’s not stuff the MapInfo controller needs to know about. The MapInfo controller knows about geocoding, which isn’t very useful for the BookInfo controller.
But both kinds of controller have something in common: their views fit into the right panel of the GetInfo popover. So there’s lots of stuff that they share in order to manage the view and in order to talk to the rest of the system. The common stuff – the stuff about being a swappable Get Info pane – belongs to the superclass. The specialized stuff – books, maps, texts, tf-idf similarity measuring – belongs in the individual pane controller.
We could do this by delegation, but we don’t want to! After all, the first thing that a Get Info pane needs to know is, “what UI elements do I control?” We don’t want to discover them through some narrow delegation API: we want them to be our properties. They’re bound to be different for each kind of pane. New panes will come fairly often; we don’t want to expand the delegate protocol every time a new flavor of pane needs some new detail.
Over in the test lab, we’ve got another little inheritance hierarchy. Some tests inherit directly from XCTestCase, but lots of tests inherit from TbxTestCase, a subclass that provides a bunch of convenience functions for making and validating test documents. A few tests inherit from another subclass, AcceptanceTestCase, which builds a really complex text fixture that’s essentially a functional Tinderbox window; this is far too cumbersome for most testing but it provides a full text fixture for functional tests.
A few weeks ago, I wrote about another example. TbxMapViewController used to have 82 different methods that performed actions in response to menus or other user interface actions. That was a mess. Instead, we have TbxMapViewControllerAction, a superclass that manages a small mountain of tiny little classes that perform one action apiece. Each subclass knows one thing and only one thing; the superclass provides lots of convenience functions so they can concentrate on what they want to do. And the superclass provides a factory so TbxMapViewController doesn’t need to know about any of the subclasses:
- (TbxMapViewControllerAction*) actionFor: (SEL) selector;
The C++ world long ago came to grips with excessive reliance on inheritance and instituted a convention: public inheritance means that the subclass is a kind of its superclass. If you inherit, you promise to do everything the superclass does, and more. If you inherit, anyone that expects the superclass should be happy to receive you. That’s restrictive – Java is far more relaxed about inheritance – but it does rein in novice excesses.
What gets confused here is a different (and still recently-discovered) code smell: subclassing framework classes is undesirable. Inheritance is intimate, and requires extensive understanding; frameworks want to keep things more distant and formal so they can hide implementation details. As the undesirability of framework inheritance has become clearer, frameworks have begun to move away from requiring applications to do this: Cocoa, with its reliance on delegation, was an early leader in this movement. But this is a separate issue concerning proper relations between framework and application; application design itself is a separate issue entirely.
You don’t need inheritance every day, but when you need it, you really do need it.