22 March 2005

Compile- and run-time polymorphism

I recently had an interesting, small-scale class design that takes advantage of some template tomfoolery. Alexandrescu's Modern C++ Design has some great concepts for compile-time polymorphism, but they're difficult to absorb without implementing. I've used policies many times before but decided to take a slightly different approach this time.

The system I'm working on has two different database tables that represent collections of objects. One table contains objects called "folders" and the other "records" (with no recursive references). The enhancement required that either object type can be declared "frozen," and that an object can be frozen multiple times. A separate table was required to store the unique property values of each successive freeze action on each record. However, because "folders" and "records" have different indexes and properties, a different freeze table was required for each. In this situation, there is distinct data types that will be acted upon in similar ways.

Once the new tables were set up, the first abstraction was to define the interface for the common actions: Add, Delete, and GetAll. Each query is different between the object types, but the actions and the data they return are consistent. This is the canonical situation for an ABC. I created the interface and derived classes (FreezeFolder and FreezeRecord), and implemented the appropriate SQL code across both objects.

This simple hierarchy simplified a client's access to both object types by abstracting their differences, but the internals of the implementation still had a considerable amount of duplication. Except for references to the index columns, a good portion of the code was nearly identical across the two objects. Duplicate code means duplicate opportunity for bugs and duplicate effort when upgrading. This was opportunity for a second abstraction using a template (compile-time polymorphism).

The template absorbed all of the code that (1) dealt with fields shared between the Folder and Record tables and (2) dealt with fields shared between the FreezFolder and FreezeRecord tables. I gave it two parameters representing the classes that implemented those two table types, and then moved and abstracted the code. The resulting template instantiations could either be inherited from or simply used as implementation members (or even pimpl members, although nothing would be "hidden" using templates with this idiom).

Although this was only a small example and the actual lines-of-code reduced was not that significant (maybe 50-60), the principle is robust and simple. In refactoring terms, the interface implementation was "Replace Conditional with Polymorphism" and the template was "Pull Up Method."

[ posted by sstrader on 22 March 2005 at 1:54:54 PM in Programming ]