Avoiding Multiple Inheritance with Traits

When reading through the literature of how games are built, we find that a common pattern for many games is the Entity-Component-System (ECS) pattern, first used in one of our favorite games Thief: The Dark Project. Tau Station uses ECS for items the characters can find and it’s proven very flexible and since we’re not a traditional “graphic” game, some of the known drawbacks of ECS don’t apply to us. However, we also make use of traditional object-oriented programming (OOP) and that’s where we wish to avoid a common trap that many software developers fall into: multiple inheritance.

To understand the issue, it’s worth considering the problem inheritance tries to solve. “Modern” object-oriented programming was actually created 50 years ago with the release of Simula-67. It had classes, polymorphism, encapsulation, and inheritance. For class-based OOP, the merits of classes, polymorphism, and encapsulation are generally agreed upon. However, the proper use of inheritance have been argued about for five decades. Inheritance, in theory, is used to create a more specialized version of a “thing”. For example, if you have a class called Mammal, you might have a child class called Cat which “inherits” all of the behavior from the parent class. This means you write less code, which is generally a good thing. Note that these classes generally aren’t done to model the real world; they model the business needs of the software.

But what’s multiple inheritance used for? Well, a duck-billed platypus is a mammal, but amongst many peculiarities, it lays eggs. How would you handle that? Well, you could copy the egg-laying code from the Bird class, but you’re a good programmer and know that cutting-and-pasting is bad. Or you might use multiple inheritance and have the Platypus class inherit from both Mammal and Bird in hopes of getting the various behaviors you want, but that runs the risk of picking up extra unwanted behaviors from the Bird class. You might extract the egg-laying code into an EggLaying class and have both Bird and Platypus inherit from that, but that’s curious because classes are generally nouns and “egg laying” is an adjective describing a behavior. You know what a bird or platypus looks like, but what does an “egg laying” look like? It doesn’t.

There are a variety of ways of approaching the problem (including composition and delegation), but in Tau Station, we use traits (known as “roles” in the Perl family of languages). A trait is essentially a set of behaviors that a class can use. It’s not a full-blown class and can’t be used as such. In our example above, we might have an EggLaying trait and both the Bird and Platypus classes can consume that trait and know how to lay eggs!

At this point, some of you may be wondering how traits are different from mixins, a programming tool originating in Lisp, but popularized by Ruby. A mixin is also a set of behaviors a class can use. While they’re better than multiple inheritance, they do share a common problem with multiple inheritance: the order in which you use the mixins matters.

Imagine, for example, you’re writing a game and you want a practical joke in the game. A character walks into a room and the practical joke needs to explode (without being lethal), after a fuse burns (for an exact amount of time). You have a Boss class which has explode() (non-lethal) and fuse() method (random). You also have a Bomb class which has an explode() method (lethal) and a fuse() method which burns an exact amount of time. So you want the Boss’s explode() method and the Bomb’s fuse() method. But if you inherit from them, then you’ll get both methods from the first class that you inherit from. But what about Ruby’s mixins?

So for mixins, the methods from the last mixin you have mixed in override the methods from the previous mixins. To pick and choose which methods you need, you’d need to instantiate separate objects and delegate off your methods to them and this is a bunch of scaffolding which is extra, annoying code you really don’t to write. If you have many behaviors you want to share with mixins, it can really be painful.

However, this “ordering problem” doesn’t exist for traits. In the above example, if we rewrote that in Perl, using traits, the code wouldn’t even compile because it doesn’t know which methods you want. Instead, you might write it like this:

With the above, we simply exclude any behavior we don’t want and the order in which the traits are consumed is not important.

By separating out pieces of behavior into traits, we make it trivial for our classes to safely share behavior. Need a logger? We have a Logging trait. Need a cache? We have a Caching trait. We have multiple unrelated classes in the game which might have an inventory (your character, storage lockers, ship’s holds, and so on), so we have an inventory trait.

Traits are our primary unit of code reuse, not inheritance. With our previous experience in simplifying complex systems with roles, it was a very natural choice for Tau Station and has much our code much easier, and safer, to work with. If you’re a programmer, find out if traits are available for your language. They are quite possibly the single greatest advance in object-oriented programming in fifty years.