Defeasible Inheritance

Few languages enforce pure property inheritance rules. They instead support a form of defeasible inheritance in which some superclass properties may be overridden in subclasses. In most languages, this overriding is limited to redefining method implementations in subclasses.

Overriding can enhance reusability. Overriding rules can make it easier to obtain code decoupling. In a system with overriding, clients cannot depend on any features of listed method code, since it may change in subclasses. (Since in most languages, only method code, not data representation commitments may be overridden, representational coupling remains a potential problem.) It also provides additional opportunities for internal reuse. A superclass may include default method implementations that are reused by most subclasses without forcing all of them to do so.

However, this form of reuse-driven inheritance can have harmful effects on other aspects of reuse and software quality. Most languages do not contain powerful enough declarative constructs to require subclass designers to preserve all essential guarantees of their superclasses. Even in languages that do possess these features, programmers tend not to use them enough. It is often so much easier to write code that implements functionality than declarations describing its effects that people don't even attempt the latter. Thus, a subclass implementation of a method may alter semantics in a way that does not preserve interoperability properties that clients depend on. As a limiting but very common case, a new class may list another as a superclass solely to reuse a few bits of its implementation without in any way preserving superclass declarative properties. (Some languages do provide constructs that distinguish at least this extreme case.)

Sometimes this form of implementation inheritance is used by developers just because it is easy to express in OO languages. Reuse goals could be better preserved via compositional techniques such as using another object as a delegated helper rather than listing its class as a superclass. Worse, the fact that many programmers see these two options as nearly equivalent can lead to other conceptual design errors. Worse still, languages, user manuals, and textbooks themselves obscure these issues by attempting to relate subclassing concepts to the use of runtime storage layout schemes that can be viewed as representationally ``embedding'' superclass objects inside subclass objects. Design decisions are sometimes based on these considerations. Sometimes this is due to an unfortunate but correct concern for representational compatibility with existing reused software (e.g., between C and C++), but more often results from simple confusion about efficiency consequences.

Defeasible inheritance is also used by programmers in order to evade language-based encapsulation rules. In most languages, a client/host is not allowed to even reference internal implementation matters listed in a class, but a subclass is. Thus, inheritance is used to simplify white-box reuse of incidental implementation features. While such code mangling is not an optimal form of reuse, it is often preferable to no reuse at all, so does have its place. It is unfortunate that OO languages force such tradeoffs against non-local reusability goals.

To the extent to which specification reuse is more important than code reuse, defeasible inheritance appears to be a net loss. There are certainly better language constructs available that still support decoupling, default code, and opportunistic white-box reuse, and even the convenience of expressing specifications, interfaces, and code in the same framework. In fact, even without adding new constructs, it is very possible to ban all use of defeasible inheritance in languages including C++ and Smalltalk, and still obtain these forms of reuse. However, the constructions are awkward and unnatural enough that programmers do not employ them in practical development.