神刀安全网

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 private , 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.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Inheritance Is Terrible

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
分享按钮