DesignedInheritance

encapsulation · API design

tags:

One of the longest running arguments on object-oriented circles is the debate between OpenInheritance and Designed Inheritance. The principle of Designed Inheritance is probably best summed up by Josh Bloch: "Design and document for inheritance or else prohibit it". With this approach you take care to decide which methods can be inherited and Seal the others to stop them being overridden.

The most common argument for designed inheritance is that since inheritance is a very intimate (and encapsulation breaking) relationship, it's easy for subclasses to effectively break their superclasses by, for instance, omitting necessary behavior when they the methods are called.

Many developers, particularly those with a EnablingAttitude find this style of argument unconvincing. Another argument that I've found more appealing is that of Elliote Rusty Harold while discussing the design principles of XOM. The point here is that "APIs are written by experts for non experts". The library writer should be well versed in the technology that the library works with. She should work to simplify this technology for library users. Encapsulation is all about hiding secrets, so a good library should hide all sorts of complications and danger points from library users, whether they use that library though calling or inheritance. So with XOM I can safely override the library classes to do what I want yet the library guarantees that it will still produce well-formed XML, without me having to worry my ugly little head about all the things that might otherwise go wrong.

Such an argument is much more convincing to me that the usual one which carries the subtext that library writers are smart and users stupid. This isn't about ability, but about detailed knowledge. I want to be as ignorant as I can be the sordid details of XML. By relieving me of needing to understand these kinds of details, I can use my brain power the actual task I want to accomplish.

Despite this persuasive argument my instinct, and the fact that I have an enabling attitude, tends to prefer open inheritance. Perhaps the crux of the problem is the mechanism we use to signal safe areas of inheritance. Usually all we have is the ability to seal classes and methods. An alternative to placate both camps would be to have an ability to overcome a seal. That way you have to go out of your way to override something that wasn't designed. If you don't explicitly open the seal, then the compiler only allows normal inheritance, but if you use the seal-opening mechanism then the compiler will hand over the trust to you - and you are responsible for the consequences. So I'd prefer to replace Josh Bloch's 'prohibit' with a 'discourage'.

Taking a broader view my guess is there's still a lot of room for improvement in how we think about interfaces, both for calling and inheritance. Ideas like Consumer Driven Contracts are needed to help us rethink what it means to have an interface definition. I don't know what the answer would be, but I think a good answer would surprise all of us.

Share: