Against Inheritance: A Better Balance for Object Orientation

send the pendulum back the other wayIt’s time to send the pendulum back the other way, says long-time programmer and author Cameron Laird. Object orientation is a good thing — but only when used in moderation.

I don’t like a lot of what’s called object-oriented programming (OOP). More to the point, it appears that common OOP practices don’t help the developers who create and maintain source code.

OOP is a good thing — in moderation. But too much OOP nowadays indulges in excess. Let’s look at how to use it in healthy balance with other development fundamentals.

We developers have barely begun to analyze development of software in a rigorous way. We have plenty of folklore — GOTO is bad, type-checking is good, code inspections work — but we have only started to learn how to judge such claims objectively, let alone perform the necessary evaluations. Object orientation is “advanced” in that it emerged in recognizable form in the mid-’60s, well after such procedural stalwarts as COBOL, Fortran, Lisp, and Algol were established. Smalltalk demonstrated significant commercial OOP success, but it was only in the ’90s that C++ and Java built on Smalltalk’s example to make OO “mainstream.” Through all this time, though, OO has proven itself more as a fashion acquisition and organizational habit than an engineering improvement. Many arguments, at least a few experiments, and occasional parodies, undercut OO’s claim to a “scientific” basis.

The first lesson to learn about OO, then, is prudent skepticism. Much of its undeniable marketplace success looks more like the (passing) adoption of corsets in Elizabethan England, than, say, the irreversible and little-regretted transition from typewriters to word processors.

Even when we have reservations about OO’s overall impact, we can, as individual practitioners, investigate how best to style our OO code. That’s the ultimate goal of this here: specific idioms to use or avoid to make OO source code as effective and maintainable as possible.

Shallow hierarchies

One fundamental aspect of OO expressiveness is class definitions in terms of inheritance. Some designers aim for inclusiveness and deep structure; they produce class hierarchies where everything is richly structured in terms of inheritance from carefully constructed base objects.

I see a profound mistake in this, if a well-motivated one. Schoolroom exercises simplify real-world situations so that classes have only a few, simple relations to each other. This leads to the habit of thinking that class definitions must have logical relations to each other. Commercial coding deals with far messier situations, where the relationship between WireTransfer and CustomerEvent is ambiguous, at best.  Many times, it’s wisest simply to leave a relationship unbound.

Another part of inheritance complication is a misapplication of DRY. “Don’t Repeat Yourself” [DRY] is perhaps one of the most fundamental programming principles, and it deserves to be. Alert designers and coders note that two classes share an attribute or method, and wisely look to abstract that commonality. The problem arrives in the exclusive reliance on inheritance to express the relation.

Inheritance certainly is a way to enforce commonality. We might, for example, sub-class everything designed to show up on a piece of paper from Printable — and I see designs that are that misguided. Inheritance is not the only way to express commonality, though.

The wisest OO commentators are unified on this point: Inheritance should be reserved for is-a relations. While OO languages are capable of inclusive use of inheritance, where everything that is a relation is modeled with inheritance, that’s a mistake. Inheritance should be exclusive, confined to the relatively few is-a relations that occur naturally in any domain.

This leads to “separated” class hierarchies, with multiple base classes. This style receives little attention in academic settings, yet it’s the right one to use in effective real-world software development.

DRY remains important, and such techniques as has-a analysis, duck typing, aspect-oriented programming, and generic types deserve as much attention in your programming as conventional inherited class definition.

We’ve been through this sharpening of focus before. For example, the arrival of “desktop publishing” was exciting. For the first time, it was practical to manage multiple fonts and other typographic treatments in low-end documents. But DTP taught us (or most of us, sigh) that just because we can announce the company picnic in eight different font families, with six different colors, doesn’t mean we should. Clean style is more about keeping only what can’t be replaced than what might do a remote good.

That’s what I mean by “exclusive” versus “inclusive” class modeling. No matter how much deep ontological research into an is-a diagram tempts you, it’s almost certain to be wasted effort. Is-a relations end up propagating their fragility to your design as a whole. Identify the is-a parts that are unmistakable, then combine them through wise duck typing, interface definitions, or a similar technique to make a better balanced, more robust, and still DRY-observant overall design.

Essential Liskov

There’s actually another step to exclusive-versus-inclusive modeling, one often neglected because it’s mistakenly regarded as merely technical: Liskov substitutability. is-a is about class definitions; Liskov substitution governs definitions of the attributes and method signatures within a class. Respect for Liskov substitution avoids unpleasant encounters with the contra-variance problem.

At an architectural level, one of our targets for good design should be the Open-Closed principle, which Liskov substitution enables.

For me, the most interesting questions in software development are less like, “Is OO good or bad?” and more like, “How do we make the best of OO?” The next steps we need with OO are clear:

  • Class diagrams need to be so simple and shallow that no one would argue there are definitional errors, not as deep and rigid as someone might argue is correct.
  • It’s time to re-study all of Bob Martin’s SOLID principles.

We’ve come as far as we can with naive approaches to overstuffing all of an application domain into a class hierarchy (what Steve Yegge calls, in one of the references above, “noun-oriented thinking”). A better balance between OO’s different aspects will give us more robust and satisfying designs and implementations.

See also:




  1. Cameron Laird says:

    I’ll be blunt: I’m so fond of brevity that, at the moment, I prefer your comment, Mark Francis Jaeger, to the whole article I wrote. This is important stuff, important enough that on some days I think we should just confine ourselves to repeating slogans like “Favor composition over inheritance”. There’s little point in refinements like Liskov substitutability when we collectively don’t get the basics right. 
    That’s my way of thanking you, Mark Francis Jaeger. I agree: it is puzzling that textbook authors, among others, persist in the imbalances you describe when it’s so long past the time we should know better.

  2. Mark Francis Jaeger says:

    It still amazes me how textbook authors get this wrong. Josh Bloch’s Effective Java taught us a long time ago to Favor composition over inheritance. Kent Beck’s Implementation Patterns decried the “pernicious use of inheritance” for “creating parallel hierarchies” and recommended delegation as a cure. still we see whole chapters on inheritance and only a small section on composition.

  3. I can confirm that inheritance is misused in many practical scenarios. Generic may be the solution in some cases, but there are many cases where excessive use of generics leads only to another flavor of bad code. 
    I think the main problem is that many languages don’t support an easy way of delegation. You need something like Scala traits, Ruby mixins or Go struct embedding to get the job done conveniently.

  4. Jaime Metcher says:

    “One fundamental aspect of OO expressiveness is class definitions in terms of inheritance.” 
    No it’s not. 
    Seriously, though, what are you saying? OO is a “fashion acquisition”? Or composition is under-emphasised? The bombshells up front don’t help the quite reasonable statements you make later on. I too prefer Mark’s version, and indeed your response to it, over the main article. 

  5. Cameron Laird says:

    If I understand, Jaime Metcher, you find me … uneven; I’m sending mixed signals. That’s probably so. I have a strong perception that the same message has to be carried in different words to reach different audiences. We all recognize that whatever I am saying, it’s not new. I have a hope, though, that this particular presentation will get through to a new cohort of readers. 
    I’m serious about the nature of programming as a species of fashion trade. I think it’s important for practitioners to be clear about which techniques are objectively superior, which are cultural conventions, which best practices, which transient fashions, and so on. I’ll be pleased if readers acquire a little skepticisim or reserve about the zealotry and salesmanship that determines so much of day-to-day development. 
    I summarize: I can well understand that the headlines and rhetoric near the front of the article “don’t help [you understand] the quite reasonable statements … later on”, but I’ve come to believe that they are essential for other readers. As Harlan Ellison used to preach (in my rough paraphrase), feel free to think of that stuff as a wrapper you’re free to discard at no extra charge. 
    The construction of the article actually ties in with another of my programming themes: my interest is not so much with intrinsic virtue–is OO a good thing? Is this article entirely consistent?–as it is with fitness for a purpose: how do we use OO in the best way for these particular requirements? Does this particular article reach this particular audience? 
    I try different treatments at different times, Jaime. The best way to thank you for your observations is to offer that perhaps something else I’ve written will serve you better. I’d love to chat with you more about your own work in … is “medical informatics” a fair label?

  6. Jaime Metcher says:

    I have no beef with rhetoric but this rhetoric doesn’t do it for me. No matter, as you say someone else will grok it. And I’ll accept your invitation to browse your impressive list of publications. 
    I couldn’t help noticing that the hyperlink on “experiments”, which I was hoping would lead to some evidence about the efficacy of OOP, in fact leads to a philosophical discussion about whether such evidence is possible to get. Are you aware of any successful efforts to produce such evidence? 
    As for medical informatics – close but not quite, my field is professional education with some emphasis on online continuing medical education.

  7. Cameron Laird says:

    YES, as it turns out, there is serious experimental evidence that bears on the effectiveness of different programming techniques. I’ll return by Monday with more details. 
    I don’t want the link you describe to be a “tease”; I’ll see what I can do about that. 
    Thanks for the real-world examples. I’m largely tied up today; I’ll need to follow up next week.

  8. Let us not confuse inheritance and dynamic polymorphism. Most developers see them as pretty much the same thing. In C++ we can use inheritance to implement class mixins – a wonderful technique when done properly. It’s probably a subset of policy based design as championed by Alexandrescu. 
    I wrote a post some time ago titled “C++ Mixins – Reuse through inheritance is good… when done the right way” that seem relevant. 

  9. Thanks, Daniel, for the reference to your description of well-crafted C++ mixins. While it’s been almost two years since you left your note here, somehow I missed it all this time. It remains *quite* relevant as we approach 2015, though.

Speak Your Mind