Friday, 24 January 2014

Data-driven class design

As if my normal posts weren't boring enough, this one is about programming. I'm a big fan of OOP (object-oriented programming) and have tried to apply it in my projects. But working with a legacy project like this I'm constantly fixing style mismatches with traditional C's linear programming style.

One of the basic ideas and building blocks in Kaduria is a data-type class that has a simple value stored (usually with accompanying enum list) and a verbose public interface. As an example there is a problem I'm working on right now. It's the mask value for mask map. In plain old C you would use enums like this:

if (mask_map->Get(x, y)==maskWall) Do_Something();

(Actually I'm using coordinate parameter for location, and x, y is used here for clarity.)

That maskWall is a "linear" or plain data. Using a datatype class things change a bit:

K_Mask_Type m=mask_map->Get(x, y);
if (m.Is_Wall()) Do_Something();

It's doing exactly the same thing, just with minor difference of using class and a getter routine. The m value is simply the mask value in the map, stored into K_Mask_Type as the only variable which then can point to the data associated to mask value. Is_Wall() can look something like this:

bool K_Mask_Type::Is_Wall()
{
  return mask_data[type].is_wall;
}

But it can also be a switch statement or any other way to handle data.

The problem with first way, old school C, is that you might want to add another mask type which is also a wall. When you do it you need to write changes in all places using that statement, for example like this:

int m=mask_map->Get(x, y);
if (m==maskWall || m==maskCorridor_Wall) Do_Something();

As you can guess, the great thing in class style is that you can add the new mask type, but Is_Wall() can include it and you don't have to search for those if statements in the source code.

It's not all good, because you can have legacy code that has both styles mixed and it can require more than just simple if statements to be changed. Also, the way the class itself is written can become complex as you add verbose functions for each of features. In this case the getter/setter syndrome is not actually that big problem, because that's the way it is supposed to work anyway. You can go wrong with class functions, too. But often they hold a logic which works through the project and when you add something it's less likely that something breaks elsewhere.

The verbosity of class functions is not something I would have thought means anything when I was still learning OOP, but it's of course a powerful way to write source code along with "class as type" classes. Often when people think of classes they are already thinking inheritance and other more advanced things that in my opinion are less important! In fact when you get to the next level you try to avoid inheritance at all costs, because it's a complex topic in which many things can go wrong, starting from the way you plan the class hierarchy. For some people it goes as far as inventing their own meta-language using templates.

I think many C++ books are to be blamed on how we see classes and immediately associate inheritance and "easy" re-use to them. Far more important is the data-driven idea: hide the raw data in the class and use (verbose) public interface to avoid refactoring through the source code when something changes. And as we know in case of roguelikes there always will be changes.

No comments:

Post a Comment