Lionel's Blog

I'm the head of engineering at Tegus, a financial research startup. I write about politics, psychology, and software.

I've worked on a few open-source projects and infrequently blog.

Recent blog posts:

Predicting the Success of Pair Programming Interviews

The Computer Boys Take Over by Nathan Ensmenger

Who by Geoff Smart and Randy Street

RSS Feed

Inheritance Is Terrible

19 March 2016

Inheritance as implemented in most object-oriented programming languages (Ruby, Java, Python, etc) is terrible. We need to stop using it.

Imagine you’re implementing software for an online store that sells books and movies. You’re adding functionality to allow users to add these items to their shopping cart. In particular, you’re writing the function that increases the user’s total purchase by the price of the item. What do you do?

The inheritance-oriented answer is something like this:

class Item
  def add_to_cart(cart)
    cart.increase_total(this.price)

class Book extends Item
  this.price = $10

class Movie extends Item
  this.price = $15

On the surface, this looks reasonable. The code to add items to the cart is shared between Book and Movie and nothing looks super complicated. You set your keyboard down and go to lunch.

Later, however, the business needs to start tracking the royalty paid to the author of a book separately from the rest of the price. For reasons internal to the book class, you really want to do something like:

class Book extends Item
  this.royalty = $2
  this.markup = $8

This obviously breaks the contact Book has with Item, though, so you need to revise your code. You end up with something like this:

class Item
  def add_to_cart(cart)
    cart.increase_total(get_price)

  def get_price
    raise NotImplementedError

class Book extends Item
  this.royalty = $2
  this.markup = $8

  def get_price
    this.royalty + this.markup

class Movie extends Item
  this.price = $15

  def get_price
    this.price

This works, and it protects Item from further changes to the way the price of a Book is calculated. But it kinda sucks.

First of all, notice how a change totally internal to the Book class forced us to change the way Movie is implemented. This happened because the superclass, Item, relied on a particular internal structure in its child classes. We’ve fixed that problem now, but it’s way more code.

Secondly, Item now operates at multiple levels of abstraction. It specifies the public interface for adding an item to a cart, but also defines the private responsibilities that something must fulfill in order to be added. Why are these descriptions in the same place? Moreover, it’s unclear if get_price is intended for public consumption or if it’s supposed to be hidden. In Java we might mark it as protected, but ideally the intended visibility would be obvious without the need for a modifier.

The better design is to separate the description of how to add an item to a cart from the description of what it means to have a price:

interface Priceable
  get_price

def add_to_cart(cart, priceable)
  cart.increase_total(priceable.get_price)

class Book implements Priceable
  this.royalty = $2
  this.markup = $8

  def get_price
    this.royalty + this.markup

class Movie implements Priceable
  this.price = $15

  def get_price
    this.price

In this design, everything operates at a consistent level of abstraction. The visibility of methods is obvious from the structure of the program rather than requiring declarations. The amount of work required to get a new class to work with add_to_cart is minimal.

For a long time I was the token Go apologist at my job. I’ve since mellowed in my enthusiasm for Go, but one thing it absolutely gets right is the lack of emphasis on inheritance in favor of interfaces. Rust has done the same thing. I hope this trend continues; I’m disappointed to see that Scala has largely preserved Java’s inheritance model.

Update

A few people have pointed out that Scala has traits, which do exactly what I want. This is true and awesome! However, Scala still has an extends keyword which functions more or less the same as in Java. I’d have preferred to see it removed.

Update

This post has generated a lot of discussion on Hacker News if you’re interested in reading more.

comments powered by Disqus