Key Handlers Revisted

"Why Encapsulation is Our Friend"

[Note: This was originally written for "FYI", the internal newsletter for the worldwide software engineering departments of Dendrite International]

As I was reading Rob's article in the last FYI about Key handlers, one thing that struck me was how sloppy a function tc_getKEY() was:

KEY pascal tc_getKEY(void)
{
	KEY	key;
	if (getKEY_handler != NULL)
		return(*getKEY_handler)();

	for(;;)
	{
		if ( (key= tc_popKEY()) == 0)
			key = _tc_getKEY();

		if (KEY_handler == NULL || (*KEY_handler)(&key) )
			return(key);
	}
}

Just look at it -- It has two separate return statements, and an infinite loop. I felt obligated to write a cleaner version. The first thing I noticed was that the part inside the for() loop is obviously the standard Get Key Handler, and that the function pointer getKEY_handler is used as a flag switching between "use standard get key" and "use special get key". Now, let us imagine us pulling that loop out into a separate function. Then tc_getKEY() could be reduced to just:

KEY pascal tc_getKEY(void)
{
	KEY	key;

	if (getKEY_handler != NULL)
		return(*getKEY_handler)();
	else
		return(tc_STDgetKEY());
}

Now, imagine what would happen if we assigned tc_STDgetKEY to getKEY_handler. Obviously, then, tc_getKEY() would call the standard Get Key function, just as it would do if getKEY_handler were NULL. So, if we could replace every instance where we set getKEY_handler to NULL, with it being set to tc_STDgetKEY instead, we could then reduce tc_getKEY() to just :

KEY pascal tc_getKEY(void)
{
	return(*getKEY_handler)();
}

Therein lies the rub. The "official documented standard" (such as it is) way to disable a special get key handler is to assign NULL to getKEY_handler. For us to change that, we would have to go through the source code and make sure the every time a special get key is removed, it's set to tc_STDgetKEY instead of NULL. And, if we miss one, the program dies -- violently.

The problem is that the very existence of the variable getKEY_handler is what's technically known as a "grubby implementation detail" and should not be a concern of the people calling the function.

OK, so how should we have done this? Well, the first thing one should realize about the original function, is that while getKEY_handler is "owned" by tc_getKEY(), anyone can modify it. In fact, everyone is expected to modify it. That should be sending up giant red flags. The first rule of encapsulation: "Never let anyone else directly touch your data". The sensible way to make such a change is through access functions, such as:

void	installGetKeyHandler(KEY (*newHandler)(void))
{
	getKEY_handler = newHandler;
}

void removeGetKeyHandler(void)
{
	getKEY_handler = NULL;
}

Then, whenever someone wanted to install a new keyboard handler, they would just call installGetKeyHandler(). To remove it, they call removeGetKeyHandler(). And, if we ever want to change any of those "grubby implementation details", say to replace the NULL with tc_STDgetKEY, no problem -- we just change them in one spot, and everything else remains the same.

"But wait," you say, isn't it more efficient to change the variable directly rather than waste time calling a function to change it? Again, no problem, just let the compiler do the work for you :

#define tc_getKEY()	(*getKEY_handler)()

#define installGetKeyHandler(newGKH) \
		getKEY_handler = newGKH

#define removeGetKeyHandler()	\
		installGetKeyHandler(tc_STDgetKEY)

Of course, if you're using C++, you don't need to use the proprcessor at all, just define the functions as inline and put them in a header file.

Now, installing & removing a function is just a fast as it ever was, but now, we have the ability to make modifications to the design. For example, we could change installGetKeyHandler(), to make sure one special handler is not already in effect before setting a new one.

Or at least, we "could" have done that, had this been designed properly to start with, but trying to change it as it exists now would be a major undertaking. All because, apparently, the programmer who designed this was too busy typing away to stop and think for two minutes on how it should be designed.

Copyright © 1994, James M. Curran