Historical context: back in the 1990s, deep inheritance hierarchies were popular. I hadn’t heard of “entity component” but my gut was telling me that inheritance hierarchies weren’t a great way to model lots of things. Here’s something I had posted to Usenet in 1997.
From: amitp@Xenon.Stanford.EDU (Amit Patel) Newsgroups: rec.games.design Subject: Re: OOP and multi-class characters Date: 4 Aug 1997 22:32:16 GMT Organization: Computer Science Department, Stanford University.
How would you implement multi-class characters in an RPG game using object-oriented C++ ?
I used to think that this was something to do with multiple inheritance. However, I’ve recently changed my mind, so I’ll explain why. (Note: this doesn’t direclty involve RPGs until the end.)
An object’s class is .. a classification. It’s a way to group similar objects together. However, not all classifications should be classes. For example, there’s a large set of people on this planet. Some of these people are currently in school, so they can be classified as ‘students’. However, this isn’t a permanent classification -- it changes from year to year, as new people enter school and other people graduate (or drop out) and leave school. If I say that Amit is a Student, that’s not a statement about the True Nature of Amit. It’s a statement about what I’m doing with my life right now.
Now the question is: should there be a Student class? Perhaps a Teacher class as well .. and then Teacher and Student can derive from Person.
Let’s start with Amit being a Student, and let’s assume Student has fields like ‘student id’ and ‘date enrolled’.
What happens if I’m a graduate student, and I decide to teach a class? Should Amit inherit from both Student and Teacher? You could use multiple inheritance here. Does the language let you change classes? (Some do, and some don’t; C++ doesn’t.) How do you initialize the ‘Teacher’ part of the existing Amit object?
What happens once the class ends, and I am no longer a Teacher? I have to change classes once again. What happens to the fields of Amit that were part of the Teacher class? (I suppose they disappear.)
Now suppose I take a class at a different school. I now have two student IDs, and two ‘date enrolled’s. Where does this information go? Can I inherit from Student twice? (That depends on the language; C++ lets you but doesn’t let you generalize to N students at run-time.) How do I refer to these fields and methods unambiguously?
Finally, I graduate and I’m no longer a student. Should my student records be destroyed?
After thinking about this example for a while, I decided that Amit should not be an instance of ‘Student’, nor should he be an instance of ‘Engineer’ or ‘Killfile Member’ or these other “temporary” classifications. Amit is-a Person. For a while, he may also be in some other categories like Student, but it’s not some essential part of his being.
So .. here’s a different design:
Amit is-a Person. Person has no subclasses.
Instead, there is an abstract Role class, which subclasses Student, Teacher, etc. Every person has-a list of Roles that he or she is in.
When I enroll in school, I create a new Student role and add it to my list. When I teach a class, I create a new Teacher role and add it to my list. When I stop teaching, I remove that role. When I enroll at a different school, I create a second Student role for my list. When I graduate, I can empty my list.
This design for students and teachers gives you more flexibility than the original one, but it may be a little harder to use. With the second design you can have many roles at once, and you can enter or leave a role without changing your object’s class.
For an RPG, I would use a similar role system. A Player wouldn’t have subclasses Magician, Thief, Ninja, Knight, and so on. Instead, each player would have a list of roles (or perhaps only one role). When you start playing, you might not have a character class. Then you can walk to the Thieve’s Guild and sign up; that would add a Thief role to your list (or set your current role to a Thief role). Later, you may want to become a magician, so you’d add a new role. Or maybe you suffer from amnesia, and you lose all your Thief abilities. You may have some kind of system where you can switch roles but you can’t play several at the same time; in that case, you can detach the role objects and attach different ones.
Earlier I said that Amit isn’t fundamentally a Student. He’s currently a student but that may not always be the case. However, he’s fundamentally a Person. You may want to consider player races to be fundamental. Then a Player would have subclasses Dwarf, Human, Elf, Orc, etc., and each player could also fill one (or more) roles.
This design has a few advantages:
- It works in C++ (i.e., it works without fancy language features like changing an object’s class, having repeated superclasses, or using multiple inheritance)
- It allows easy addition and removal of roles.
- It allows playing a role multiple times. (For example, you may be a Thief in two different guilds.)
It suffers from:
- In some ways it’s more complicated than the inheritance-based design. For example, methods on the Player class may have to be forwarded to one or more roles.
- It seems unintuitive at first (to many people). There’s a book called Mathsemantics that deals with some of these issues about an object being fundamentally one thing vs. an object playing a role (called an ‘event’ in that book). [The author seems to dwell on ‘passengers’ on airplanes not being the same as ‘persons’, since you can be a ‘passenger’ many times but a ‘person’ just one. Anyway, although it had nothing to do with OO design, I learned a lot of design by reading it.]
This probably fits into the general inheritance vs. composition design decision. Inheritance tends to be easier to program, but composition tends to be more flexible.
Okay, I’m done ranting.
Postscript added 3 July 1998, updated 13 February 2000: After a while I discovered that other people have thought about this too. See for example this web page about roles and design patterns.