Saturday 17 January 2009

Item 17: Design and document for inheritance or else prohibit it

This item is directly related to its predecessor, continuing the discussion of the issues around inheritance. In common with the previous item, and as pointed out by Jan in the excellent summary, the topic under discussion is fairly common and well-known in the OO community. The advice is no less useful for that, though I have my reservations about the item. It is useful to bear in mind that in this pair of items the author isfocusing on inheritance from ordinary concrete classes across package boundaries.

The item begins by describing how to design, and in particular how to document, a class for inheritance. I would have preferred a little more emphasis upfront on the wisdom (or otherwise...) of inheriting from a concrete class. Although the item concludes that designing a class for inheritance places substantial limitations on the class, and does make the recommendation to prohibit subclassing in classes that are not designed and documented to be safely subclassed, I would favour a stronger message here. Prohibit Inheritance By Default, perhaps (see item 33 of Meyers' More Effective C++: Make non-leaf classes abstract). The author notes that his advice as it stands may be somewhat controversial, so I get the feeling he is pulling his punches.

One could suggest that any programmers who have grown accustomed to subclassing ordinary concrete classes would benefit from such a punch - just to knock some sense into them, of course. Two simple methods of prohibiting subclassing are mentioned: declaring the class final, or keeping the constructors private or package-private. As the latter approach was discussed in a previous item, it is reasonable that this section is brief, however it does add to the feeling that this important advice is not given enough weight.

Flipping back to the start of the item, design and documentation for inheritance is discussed. Four main guidelines are presented. Firstly, if a class may be inherited from, then the effects of overriding any method should be documented. For each public or protected method, an indication of which overridable methods are invoked, and how, should be given; in other words, the classes self-use of overridable methods should be publicly documented. Reinforcing the message that inheritance violates encapsulation, this leads to the unfortunate result of API documentation describing implementation details - not just what the method does, but also how it does it. Not mentioned in the book is the dependency this introduces on keeping the documentation in step with the code.

The author also covers the idiom of providing hook protected methods for derived classes to override, allowing specialisation of certain steps within an overall task. Again, I feel uncomfortable about this as general advice; while the example given sounds reasonable, I'm not convinced about the universal applicability of this practice. He goes on to make the sensible suggestion that the best way of testing your class's suitability for inheritance is to write some subclasses. Clearly, this will help smoke out any issues in your design.

Finally, the straightforward guidance that constructors must not invoke overridable methods (and neither should constructors-like methods such as clone() and readObject()). This is a bad idea for the same underlying reason as calling virtual functions in a C++ constructor is, although the two languages differ in their behaviour (see item 9 of Effective C++ for the C++ angle), but the common message is that calling down to parts of an object that have not yet been initialized is inherently dangerous. This at least is indisputable, so in this case I will not take issue with the author.

Ewan Milne

No comments: