t e m p o r a l 
 d o o r w a y 

The Converter Pattern

 

Patterns

Patterns are currently a hot topic in the object-oriented programming community. Patterns are abstractions which encapsulate a "way of doing things" that extends beyond a single class. They represent likenesses between classes which are not related by inheritance, or the pattern of interoperation between classes, often classes within frameworks, but sometimes across frameworks.

The Converter Pattern

The Converter pattern is one which appears in a number of different places, but for this example, we will start with the TStringList class of C++ Builder's VCL.

TStringList is essentially a variable sized array of AnsiString, with the elements addressible using a 0 based index. However, where the converter pattern comes in is in the property "CommaString".

If you assign a comma delimited ASCII string to CommaString, it will automatically be parsed into the Strings of the TStringList. By the same token, if you assign the value of CommaString to some other AnsiString variable, that variable will contain a comma-delimited "unparse" of the elements in the TStringList's Strings.

Once you have assigned a value to CommaString, you can immediately access the fields of the string by a statement such as

myStringList->Strings[Index];

So here we have the concrete basis of the pattern. Let's extend this example, by adding other translation capabilities to TStringList.

The Extended TStringList

Here's a header file:

class FieldParser: public TStringList
{
   public:

      __fastcall FieldParser(void);
      __fastcall ~FieldParser(void);

   published:

      __property SpaceText = {read=GetSpaceText, write=SetSpaceText};
      __property TabText = {read=GetTabText, write=SetTabText};

      __property CharacterText = {read=GetCharacterText, write=SetCharacterText};

   private:

      AnsiString   __fastcall GetTabText(void);
      void         __fastcall SetTabText(AnsiString theTabText);

      AnsiString   __fastcall GetSpaceText(void);
      void         __fastcall SetSpaceText(AnsiString theSpaceText);

      AnsiString   __fastcall GetCharacterText(void);
      void         __fastcall SetCharacterText(AnsiString theCharacterText);
};

The implementation is relatively simple:

...
#include <vcl.h>
#pragma hdrstop

#include "FieldParser.h"
...
__fastcall FieldParser::FieldParser(void)
{
}
...
__fastcall FieldParser::~FieldParser(void)
{
}
...
AnsiString __fastcall FieldParser::GetTabText(void)
{
   AnsiString   Result;

   for (int Index = 0; Index < Count; Index++)
   {
      Result+= Strings[Index];
      if (Index < Count) Result+= "\t";
 };

 return Result;
}
...
void __fastcall FieldParser::SetTabText(AnsiString theTabText)
{
   Clear();

   AnsiString   WithCommas;

   for (int Index = 0; Index < theTabText.Length(); Index++)
   {
       WithCommas+= (char) ((theTabText[Index+1] == '\t') ? ',' : theTabText[Index+1]);
   };

   CommaText = WithCommas;
}
...
AnsiString __fastcall FieldParser::GetSpaceText(void)
{
   AnsiString   Result;

   for (int Index = 0; Index < Count; Index++)
   {
      Result+= Strings[Index];
      if (Index < Count) Result+= " ";
   };

   return Result;
}
...
void __fastcall FieldParser::SetSpaceText(AnsiString theSpaceText)
{
   Clear();

   AnsiString   WithCommas;

   for (int Index = 0; Index < theSpaceText.Length(); Index++)
   {
       WithCommas+= (char) ((theSpaceText[Index+1] == ' ') ? ',' : theSpaceText[Index+1]);
   };

   CommaText = WithCommas;
}
...
AnsiString __fastcall FieldParser::GetCharacterText(void)
{
   AnsiString   Result;

   for (int Index = 0; Index < Count; Index++)
   {
      Result+= Strings[Index];
   };

   return Result;
}
...
void __fastcall FieldParser::SetCharacterText(AnsiString theCharacterText)
{
   Clear();

   AnsiString   WithCommas;

   for (int Index = 0; Index < theCharacterText.Length(); Index++)
   {
      WithCommas+= theCharacterText[Index+1];
      if (Index < (theCharacterText.Length()-1)) WithCommas+= ',';
   };

   CommaText = WithCommas;
}
...

Note how we leverage what is available from the ancestor class - for instance, we convert incoming strings to comma delimited strings and then assign them to CommaText, which takes care of actually splitting the string - why reinvent the wheel, after all?

From this, you can see the core of the pattern: A central data structure which is the source for all of the "get" methods, and which can be fed, in varying ways, by the "set" methods. Remember, however, that the various settable property formats should be well-suited to being part of the same object. For instance, a date parser might better be implemented in its own class, rather than as an augmentation of this class, since it is unlikely that a TabText, CommaText or SpaceText would be interesting representations of a date.

Copyright © 2004 by Mark Cashman (unless otherwise indicated), All Rights Reserved