topnav
masthead

Series Info...#5: The Tragedy of Objects

by Richard Bartle
October 31, 2001

Inheritance as a software tool gained its popularity as part of the Object-Oriented Programming (OOP) drive of the 1980s. OOP is great for large projects involving many programmers, because it allows whoever codes a class to define its functionality separate from that of the rest of the system, presenting only a set of interface routines for other classes to use for access. This stops programmers from stomping all over each other’s code, and makes modifying existing code easy.

Well that’s the theory. In practice, well, I’ll talk about that shortly...

OOP is a methodology in which data and code fragments are attached to objects that are instances of classes. Each object comes with a set of data members and member functions (or methods). A principle of good practice known as encapsulation states that access to the data members should only occur through the member functions, so therefore to all intents and purposes we can consider methods as the Big Thing as far as OOP is concerned.

Methods are pieces of code defined for individual classes. They can be inherited by sub-classes, and will be attached to specific instances when their classes are created as objects in the program. How methods are implemented is unimportant; what matters is how they are called.

In the Microsoft Foundation Class library, for example, to stop a particular window from being overlapped by any other a programmer could invoke window.BringWindowToTop(). This would work for any instance of window. Whether the BringWindowToTop method is programmed the same way for pop-up windows, dialogue boxes and splitter windows, who cares? It does the job transparently to the programmer who issued the call, using whatever method is appropriate for the window concerned.

That’s the good news, now the bad...

Fragility

OOP is design-critical. If you set about programming a system and your object tree is specified wrongly, correcting it can be a horrendous task. For example, a model of the human body using organs as objects, each with its own specific functionality, might do 95% of what you wanted it to do. That’s still only 95%, though. A model based on the central nervous system might do 100% of what you wanted. If you didn’t find this out until late, however, then reprogramming all the organs code you’ve written as methods for nerve pathway objects would be... well let’s just say you’d be clenching your teeth awhile.

OK, so back to MUDs. Because it comes with inheritance, you want to write a MUD using an object-oriented approach. That’s fine by me, there’s nothing wrong with that at all. So what are your objects? Most MUD designers don’t give this a moment’s thought. Why, the objects in the game, of course! It has treasure and swords and doors and boxes and rooms and mobiles and players — those are all objects, it’s obvious.

Well they are "objects", yes, but they don’t have to be OOP objects. They get you 95% of what you can have, but not 100%. The fact that people call both concepts "objects" is the cause of a major dead-end in MUD programming.

Alternative Objects

Let’s say we want to implement a HIT command. If you hit a creature (i.e. a player or mobile) then a message is generated and a fight starts. That would be a simple method, invoked by creature.hit(). How about a PUNCH command? That would be the same as HIT, but with a different message ("name punches you" instead of "name hits you"). OK, so we need a creature.punch(), which would probably call some generic creature.hit("punches") routine. The same applies when we decide to add SLAP, THUMP, WHACK, SMITE and anything else the players might try (my not particularly good thesaurus offers 22 fairly non-contentious alternatives).

Now what if hitting something other than a creature had a different effect? HIT HARDOBJECT (e.g. an anvil) might hurt you, HIT SOFTOBJECT (e.g. a cake) might damage what you hit, HIT DOOR might transmit a knocking sound to the other side and HIT BAG might generate some suggestion of what’s inside it. OK, well we can write special methods for hardobject.hit(), softobject.hit(), door.hit(), bag.hit() and any other classes we want.

Then we have to add hardobject.punch(), hardobject.slap(), hardobject.thump(),... bag.smite(). That’s an awful lot of repetition.

The problem is that although game objects are OOP objects, their methods aren’t. We’re back to the old enumeration days again, when I was writing the same line of code over and over again saying that when the goat came across something it wanted to eat it would eat it. What we need here is to be able to quantify over methods, i.e. define a class HIT of which PUNCH, SLAP, THUMP etc. are instances. We can’t, though, because functions aren’t OOP objects. Making them be OOP objects would entail a complete rewrite.

That’s why I’m telling you this: so you know the score before you start.

Commands

The real OOP objects in a MUD are the commands. It isn’t a property of objects of class CREATURE that when you hit them a message is generated and a fight begins. Neither, incidentally, is it a property of objects of class HIT that if a CREATURE is the victim then that happens. Rather, it is a property of the command { HIT CREATURE } that there’s a message and a fight. It’s { HIT CREATURE } that has the method — not CREATURE, not HIT.

This simple observation completely opens up a MUD’s design. You can use it for all kinds of things. You don’t have to philosophize whether OPEN DOOR WITH KEY means door.open(key) or key.open(door) — it means { OPEN DOOR KEY }.code().

Unfortunately, I’m all out of space right now, so I can’t give examples of how versatile this formalism is. That comes in the article to follow.

You’re on the edge of your seat with expectation, I can tell...

Recent Discussions on Notes from the Dawn of Time:

jump new