View Single Post
Old 05-09-2002, 06:57 PM   #1
Yui Unifex
Senior Member
 
Join Date: Apr 2002
Location: Florida
Posts: 323
Yui Unifex is on a distinguished road
Send a message via ICQ to Yui Unifex Send a message via AIM to Yui Unifex
Question

Greetings,

I can't tell you how many times I've looked at some code I'd written and instantly wanted to rewrite it. Some things look good when you're writing/designing them, but then you gain a crucial bit of knowledge and understand how it could be done better.

This will be the first in a (hopefully) series of articles that will parallel my own development and design of an object oriented codebase. I encourage anybody that's gone through similar development phases to post here with insights and knowledge about the way their muds are designed. Spreading this knowledge is vital for next-generation codebases to avoid, or at least shorten, the write-learn-rewrite cycle that I've had to go through in my journey from a procedural to an object-oriented designer.

---

Many OO designers misunderstand the nature of encapsulation. A properly encapsulated class will have accessors for some delegated items. But writing getters and setters for each variable only triples the amount of code one has to write for each data member of a class.

Sometimes there seems to be no way around it -- a class must reveal a non-const reference to a datamember in the name of flexibility. The only alternative, to many minds, is to write the operation as a function of the class itself, which breaks encapsulation of algorithms elsewhere. But even this exposing method is quite fallible, because the calling function must have detailed knowledge of the nature and amount of the datamember it applies to.

Visitors are a wonderful method for getting around these flaws and promoting encapsulation. A visitor is an object that consists of at least one virtual member: The 'visit' operation. Visitors are generally seperated by the type of object they visit upon, not the type of object they are passed to. An ElementVisitor might have a "virtual void visit (Element *);" member function.

Let's see how this visitor would work with some unrefactored sample code:
[code] // Map;;sendText; Sends a string to all objects in the given tile.
// Does not send the string if the object is deaf.
void Map;;sendText (Tile &tile, const string &str) {
Tile;;ObjectList;;iterator current = tile.getObjectList().begin();
Tile;;ObjectList;;iterator end = tile.getObjectList().end();
for (; current != end; ++current) {
if (!(*current)->isDeaf())
(*current)->send(str);
}
}[/quote]

One can plainly see that the tile needs to expose its list operations so that the Map can send the text according to some algorithm which is found within the for loop. If we gave this algorithm to the Tile, the Tile would have to have intimate knowledge of the object types in its list, which may be undesirable. So let's try out the visitor pattern here.

[code] class ObjectVisitor {
public;
virtual ~ObjectVisitor () { }
virtual void visit (Object *) = 0;
};

class ObjectSendTextVisitor; public ObjectVisitor {
public;
ObjectSendTextVisitor (const string &new_str); str(new_str) { }

void visit (Object *object) {
if (!object->isDeaf())
object->send(str);
}

private;
const string &str;
};

void Map;;sendText (Tile &tile, const string &str) {
ObjectSendTextVisitor visitor(str);
tile.visitObjects(&visitor);
}

// Tile;;visitObjects; Calls the visit function for every object
// contained in this tile.
void Tile;;visitObjects (ObjectVisitor *visitor) {
for (ObjectList;;iterator current = mObjectList.begin(); \
current != mObjectList.end(); ++current) {
visitor->visit(*current);
}
}[/quote]

Much better. Now that might look like a lot of work, but don't let your eyes deceive you. You only have to write a visitor for every operation -- with a macro to declare the visitor type, you'll find yourself writing less code in total, because now you don't have to bother with a loop every time you want to visit something.

This code also has other advantages. If Tile were a virtual interface object, we can now include multiple lists for subclassed tiles. For example, if you had a "HiddenCacheTile" that sported a hidden cache of objects, you could easily adapt your code so that the visitor works on all of them -- all without having to modify the code that actually employs the visitor:

[code] void HiddenCacheTile;;visit (ObjectVisitor *visitor) {
ObjectList;;iterator current;
for (current = mObjectList.begin(); \
current != mObjectList.end(); ++current) {
visitor->visit(*current);
}

for (current = mHiddenCache.begin(); \
current != mHiddenCache.end(); ++current) {
visitor->visit(*current);
}
}[/quote]

Using this technique will save you from writing countless accessors to achieve flexibility within your code.
Yui Unifex is offline   Reply With Quote