Please note that I don't update this web site anymore. You can visit my new personal blog at www.williamwilling.com.
Composition vs. Inheritance
Wednesday, October 26, 2005
Gianfranco ran into the inheritance vs. composition issue.
I spent the evening working on the project, and I was going to try to get the Ball to move in four directions. I thought that I should probably start by laying the groundwork and having Player and Ball inherit from a class called Entity.
My thinking was that Entity would control the movement of the object in question. It would set the position, move in a certain direction, and also control the KrSprite pointer. Then Player and Ball could inherit from Entity and have all of the functionality available, and I would be able to handle the code in one place instead of two or three.
Sounds reasonable, right? It's a common way of thinking in object oriented programming: if we need that code in several places, let's abstract it into a base class. Often however, that's not necessary and if it isn't necessary, you should probably avoid it. In C++ Coding Standards: 101 Rules, Guidelines and Best Practices Herb Sutter and Andrei Alexandrescu talk about this issue in rule 34.
Prefer composition to inheritance
Avoid inheritance taxes: inheritance is the second-tightest coupling relationship in C++, second only to friendship. Tight coupling is undesirable and should be avoided where possible. Therefore, prefer composition to inheritance unless you know that the latter truly benefits your design.
They go on to discuss the advantages composition has over inheritance and tell you when you should prefer inheritance to composition, but I don't feel like typing all that out. The book is actually full of good advice, so if you program in C++ regularly, you might as well consider buying it.
Putting the above guideline and Gianfranco's experience together, we can say that inheritance isn't the solution to the Ball and Player problem. So, how can we solve this problem? Here's my take on it.
First, let's look at the actual problem. We have a Ball class and a Player class that appear to share some traits. These traits are:
- Both are represented by a sprite.
- Both are movable.
These two traits are clearly different and I don't think it's a good idea to put both of them in the same class. Rather, I'll tackle them both seperately.
The sprite case isn't actually all that difficult. All you need to ask is: is a Player a sprite? No, it isn't: no inheritance. Does a Player have a sprite? That makes more sense actually, since a player is represented by a sprite. You can even have different sprites for different Player-objects. Composition seems like the right choice here.
Adding motion is a bit different. When we're talking about sprites, we're talking about data, but motion is functionality. Before you abstract code, any code, make sure the abstraction is called for. In other words, before you decide that Ball and Player should share the same code for motion, make sure that both Ball and Player require the exact same code for motion.
The easiest way to check this is to just implement the code for one of them and then copy it over to the other. That's right, just good old copy-paste it. You don't have to think hard about it and it's easy to do. You can always abstract a base class later, if you need. Right now, you need to check whether Ball and Player really require the exact same motion code.
In my experience, motion is a good example of functionality that looks like a good candidate for abstraction, but that differs from object to object in reality. Does a ball move in the same way a player does? Probably not. Even if they share the same freedom of motion, that doesn't mean that they are moved by the same forces. Then again, they might in your game. How to find out? Try it. Just copy-paste.
There is one ability you lose if you don't create a base class with the motion code. You might want to put all your game objects in a list and iterate over them every frame to update their positions.
(The following code is pseudo-code, not actual C++.)
foreach(obj in gameObjects)
You can't do this without a base class; you'd need a trouble-some switch-statement to check for the type of each object, then cast the object to it's own type and then call the Move-function. Not pretty.
The solution is to create an interface instead of a base class. An interface does specify functions, but those functions aren't implemented. In C++, that means creating a base class that has only pure virtual functions.
(The code that follows now, however, is actual C++ code. :-))
virtual void Move() = 0;
Player and Ball can inherit from this class and still define their own ways of moving around. The Update-function can now call Move() without all that nasty switching.
Back to blog index
Tell me what you think
Since I'm not updating this site anymore, I disabled comments. You can visit me at my new site: www.williamwilling.com.