I l@ve RuBoard previous section next section

Item 23. Class Relationships—Part 2

Difficulty: 6

Design patterns are an important tool in writing reusable code. Do you recognize the patterns used in this Item? If so, can you improve them?

A database manipulation program often needs to do some work on every record (or selected records) in a given table, by first performing a read-only pass through the table to cache information about which records need to be processed, and then performing a second pass to actually make the changes.

Instead of rewriting much of the common logic each time, a programmer has tried to provide a generic reusable framework in the following abstract class. The intent is that the abstract class should encapsulate the repetitive work by first, compiling a list of table rows on which work needs to be done, and second, by performing the work on each affected row. Derived classes are responsible for providing the details of their specific operations.

//--------------------------------------------------- 
// File gta.h
//---------------------------------------------------
class GenericTableAlgorithm
{
public:
  GenericTableAlgorithm( const string& table );
  virtual ~GenericTableAlgorithm();

  // Process() returns true if and only if successful.
  // It does all the work: a) physically reads
  // the table's records, calling Filter() on each
  // to determine whether it should be included
  // in the rows to be processed; and b) when the
  // list of rows to operate upon is complete, calls
  // ProcessRow() for each such row.
  //
  bool Process();

private:
  // Filter() returns true if and only if the row should be
  // included in the ones to be processed. The
  // default action is to return true (to include
  // every row).
  //
  virtual bool Filter( const Record& );

  // ProcessRow() is called once per record that
  // was included for processing. This is where
  // the concrete class does its specialized work.
  // (Note: This means every row to be processed
  // will be read twice, but assume that that is
  // necessary and not an efficiency consideration.)
  //
  virtual bool ProcessRow( const PrimaryKey& ) =0;

  struct GenericTableAlgorithmImpl* pimpl_; // MYOB
};

For example, the client code to derive a concrete worker class and use it in a mainline looks something like this:

class MyAlgorithm : public GenericTableAlgorithm 
{
  // ... override Filter() and ProcessRow() to
  //     implement a specific operation ...
};
int main()
{
  MyAlgorithm a( "Customer" );
  a.Process();
}

The questions are:

  1. This is a good design and implements a well-known design pattern. Which pattern is this? Why is it useful here?

  2. Without changing the fundamental design, critique the way this design was executed. What might you have done differently? What is the purpose of the pimpl_ member?

  3. This design can, in fact, be substantially improved. What are GenericTableAlgorithm's responsibilities? If more than one, how could they be better encapsulated? Explain how your answer affects the class's reusability, especially its extensibility.

I l@ve RuBoard previous section next section