Handle RDD's - The Class(y) Way

In CA-Clipper 5.2 we have been introduced to the new Order
Management function series.  Upon first glance these new
functions may appear to complicate the issue of Index
manipulation.  However, after some consideration you may agree
that it's the best solution to provide uniformity to RDD
handling.

Like the introduction of any new concept we often ask, not how
can we directly implement these new features but instead, how can
we map the new features to our acquired knowledge.  The solution
is to make a move towards Object Orientation.  With Polymorphism
(overloading) and Dynamic Binding inherent in Object Orientation,
we can implement the new Order Management features transparently
(ie. achieve the same results by redefining the older Index
manipulation methods in a new descendant class).

OOP's, where has it gone?
WhOOPs, did he mention OOP.  It appears that for some
inconceivable reason Object Orientation is taking a back seat for
many developers among our CA-Clipper community.  Object
Orientation is no new paradigm, in fact all Clipper developers
are using the basic concept, without even realizing it.  If you
have ever used the CA-Clipper alias operator "->", then you have
effectively sent a message to an object, which is the fundamental
principle upon which this paradigm is based.

Eg 1. (Assume 01Cust Database exists with an Address Field)

Public dToday := Date()  // Create a Memory Variable.
Use 01Cust New           // Open the 01Cust database.
01Cust->( dbGoTop() )    // The 01Cust object has been sent a message dbGoTop().
? 01Cust->Address        // The 01Cust object has been sent a message to retrieve the value
                         // in the Address field.
? Field->Address         // The General Field object has been sent a message to retrieve the
                         // value in the Address field.
? M->dToday              // The General Memory Variable object has been sent a message to
                         // retrieve the value of dToday.

As you can see Object Orientation is naturally inherent in
Database/Data manipulation.  In the above example the only
messages that are specific to the 01Cust Database are the 01Cust
database field names, as opposed to other databases.  All
database manipulation functions/messages will perform the same
result despite which database you are working with.  Therefore,
if we define our own Work Area class we can manipulate the Work
Area objects generically.  WhOOPs, we've just stumbled on
'Genericity'.  Furthermore, the syntax to implement manipulation
on the Work Area object, would be practically identical to our
former implementation.


Eg 2.
Local oCustWA := TWAreaDBF():New(...)   // Instance a new Customer Work Area Object.
oCustWA:GoTop()                         // Go to the Top record in the oCustWA object.
? (oCustWA:cAlias)->Address             // Print the Address field in oCustWA Object.
                                        // Compare the above to the following :
Use 01Cust New                          // Open the 01Cust database.
01Cust->( dbGoTop() )                   // Go to the top of the 01Cust Database.
? 01Cust->Address                       // Print the value in the Address field in 01Cust
                                        // Database.
 Currently out of Date!
Ok, so apart from encapsulation of a database and its optional
Indexes/Tags (Work Area), what other benefits are there?  The
most significant benefit is that we can eliminate the limitations
behind CA-Clipper's "Current" work area concept.

Eg 3. (Assume 01Cust Work Area exists with field Cust_Id, 02Sales Work Area exists and has both
Cust_Id and Inv_No fields).

Local oCustWA  := TWAreaDBF():New(...)       // Instance a new Customer Work Area Object.
Local oSalesWA := TWAreaDBF():New(...)       // Instance a new Sales Work Area Object.
oCustWA:GoTo(10)                             // Go to the 10th record in the oCustWA object.
oSalesWA:Seek( (oCustWA:cAlias)->Cust_Id )   // Search for the Cust_Id value in oCustWA object in
                                             // the oSalesWA object.
? (oSalesWA:cAlias)->Inv_No                  // Print the Inv_No value in the oSalesWA object.
                                             // Compare the above to the following :
Use 01Cust Index 01Cust New                  // Open the 01Cust Work Area.
Use 02Sales Index 01Sales New                // Open the 02Sales Work Area.
Select 01Cust                                // Make the 01Cust Work Area "Current".
GoTo 10                                      // Go to the 10th record in the "Current" Work Area.
Select 02Sales                               // Make the 02Sales Work Area "Current".
Seek 01Cust->Cust_id                         // Search for Cust_Id of 01Cust, in the "Current"
                                             // Work Area.
? 02Sales->Inv_No                            // Print the Inv_No value in the 02Sales Work Area.

Now you're probably looking at this code and exclaiming, 'what's
the point?'.  The point is that we have become accustomed to
developing applications where the Program has control.  Today's
Windows Applications demand that the End-User have control and
thus emerging is a shift from Structured Programming to Event
Driven Programming (modeless operation).

Consider the following:
You have developed a Membership System for the local golf club.  The software is a Windows
executable and the manager is currently operating it.  The manager is viewing the Customer
database table when he notices a dubious client.  He leaves the Customer table window on the
screen and further selects to additionally view both the Account and Competition database tables.
The screen now displays 4 overlapping windows including the application window.  As the manager
switches between tables, the system must recognize which database to activate.  The simplest
solution is to implement each table as an object, and thus any request made to a table will cause
a message to be sent to that object.


What about RDD's?
Now the question should not be, how do we handle RDD's, but how
do we handle any structured data storage medium?  The answer is
embedded in our Work Area class hierarchy.  That is, by defining
our base Work Area class we can inherit some features, redefine
other features and add new features as we approach new mediums.
So currently a Work Area can range from a database with optional
Indexes/Tags, to a Text File, to a n-dimension array, to a
conceptually simulated array (series Range in n-dimensions).
Thus, in this context, Work Area refers to a generic object
defining a structured data storage medium.



                        TWAreaArray    Ŀ
                        TWAreaCSA      Ĵ
                        TWAreaTextFile  TBaseWArea
     TWAreaRDD    TWAreaDBF      

          (Fig 1. Work Area class hierarchy)


     Eg 4.

     Local oCSAWA      := TWAreaCSA():New(...)      // Instance a Conceptually Simulated Array.
     Local oArrayWA    := TWAreaArray():New(...)    // Instance an Array Work Area.
     Local oTextFileWA := TWAreaTextFile():New(...) // Instance a Text File Work Area.
     Local oCustWA     := TWAreaRDD():New(...)      // Instance a FoxPro Work Area.

     oCSAWA:GoTop()                                 // Move pointer to the 1st element in the series.
     oArrayWA:GoTop()                               // Move pointer to the 1st element in the array.
     oTextFileWA:GoTop()                            // Move pointer to go to beginning of the file.
     oCustWA:GoTop()                                // Move pointer to go to the top of FoxPro Database.


What, a generic TBrowse, CA'MON!
In the above example we saw how four different Work Area objects
respond differently when sent the same message.  The beauty in
this polymorphic behavior can be illustrated when examining an
extended TBrowse class which holds "oWArea" as one of its
exported instance variables.  Discussing the details of the
extended TBrowse class is out of the scope of this article
although lets assume that it uses the oWArea (Work Area object)
as its navigational engine.  Thus by instancing an extended
TBrowse object we can view any Work Area medium, without any
additional TBrowse handler code.

Create Class TWAreaDBF From TBaseWArea       Create Class TWAreaRDD From TWAreaDBF
 Local     :                                    Local     :
 Protected :                                    Protected :
   Method  ChkIdx                                   Method  ChkIdx
 Export    :                                    Export    :
   Var     aDb                                      Method  ClearIndex
   Var     aIdx                                     Method  CreateIndex
   Var     aSessionDelRec                                Message IndexKey     Method MyIndexKey
   Var     aStruct                                  Method  IndexOrder
   Var     cAlias                                   Message OrdBagName   Method MyOrdBagName
   Var     cDriver                                  Message OrdBagExt    Method MyOrdBagExt
   Var     cName                                    Message OrdCreate    Method CreateIndex
   Var     lisIndex                                 Message OrdKey       Method MyIndexKey
   Var     lReadOnly                                Message OrdListClear Method ClearIndex
                                                    Message OrdName      Method MyOrdName
   Method  Append                                   Message OrdNumber    Method MyOrdNumber
   Message Bof         Method MyBof                 Method  SetIdxOrder
   Method  ClearIndex                               Method  SetIndex
   Method  CloseArea                                Method  SetOrder
   Method  Create                               EndClass
   Method  CreateIndex
   Method  Commit
   Message Delete      Method MyDelete
   Message Eof         Method MyEof             // Base Work Area Class establishes a standard
   Message fLock       Method MyfLock           // structure to navigate any generic Work Area.
   Method  GoBottom                             //  All Methods are Differed.
   Method  GoTo
   Method  GoTop                                Create Class TBaseWArea
   Method  IndexOrer                              Local     :
   Message IndexKey    Method MyIndexKey          Protected :
   Method  lisEmpty                               Export    :
   Method  nLastRec                                 Var     xExpr2Srch
   Method  nRecNo
   Method  Open                                     Message Bof         Method DeferMethod
   Method  Pack                                     Message Eof         Method DeferMethod
   Method  Recall                                   Message GoBottom    Method DeferMethod
   Method  ReIndex                                  Message GoTo        Method DeferMethod
   Message rLock       Method MyrLock               Message GoTop       Method DeferMethod
   Method  Seek                                     Message IndexKey    Method DeferMethod
   Method  SeekLast                                 Message nRecNo      Method DeferMethod
   Message Select      Method MySelect              Message Seek        Method DeferMethod
   Method  SetDriver                                Message SeekLast    Method DeferMethod
   Method  SetIdxOrder                              Message Skip        Method DeferMethod
   Method  SetOrder                                 Message SetOrder    Method DeferMethod
   Method  Skip                                     Method  DeferMethod NULL
   Method  Struct                               EndClass
   Method  UnLock
   Method  Use
   Method  Zap
   Class Method CommitAll
   Class Method CloseAll
   Class Method UnLockAll
 EndClass
 (Fig 2. Class Dictionaries for TWAreaDBF, TWAreaRDD & TBaseWArea - Syntax is Class(y) Ver 1.05)

Checking for all system files and creating or re-creating them is
a common procedure in most applications.  Through genericity we
can avoid the necessity of redundant checking code using the Open
& ChkIdx Methods.  Furthermore, dynamic binding enables us to
make the Order Management function series transparent:

Ŀ
      Virtual                       Invoked Execution Map                     
   Work Area Methods                                                          
Ĵ
                         TWAreaDBF              TWAreaRDD                    
Ĵ
 ClearIndex()            dbClearIndex()         OrdListClear()               
 CreateIndex(...)        dbCreateIndex(...)     OrdCreate(...)               
 MyIndexKey(...)         IndexKey(...)          OrdKey(...)                  
 IndexOrder()            IndexOrd()             OrdSetFocus()                
 SetIndex(...)           dbSetIndex(...)        OrdListAdd(...)              
 SetOrder(...)           dbSetOrder(...)        OrdSetFocus(...)             


   (Fig 3.  Translation of Virtual Methods in descendant classes)


Eg 5. (Example parameters passed to TWAreaRDD)

//                    cDriver   cName         cAlias    lShared  lReadOnly
#DEFINE aCLIENT_DBF   { "DBFCDX", "CLIENT.DBF", "client", .T.,     .F.,;
                       { /* aStruct */              ;
                         {"CLIENT_CD ", "C",  8,  0},;
                         {"SURNAME   ", "C", 15,  0},;
                         {"FIRSTNAME ", "C", 10,  0},;
                         {"TITLE     ", "C",  4,  0},;
                         {"STREET    ", "C", 30,  0},;
                         {"SUBURB    ", "C", 15,  0},;
                         {"PCODE     ", "C",  4,  0},;
                         {"PHONE_HM  ", "C", 15,  0},;
                         {"PHONE_WK  ", "C", 15,  0} ;
                       };
                      }

#DEFINE aCLIENT_CDX                                              ;
   {                                                        ;
       { "CLIENT",            /* cIndexName or cOrderBagName */                 ;
         { /* cOrdName     cKeyExpr    bKeyExpr                 bIndexCond   lUnique */      ;
           { "CLIENT_CD", "CLIENT_CD", {|| client->client_cd }, {|| .F. }  , .f.},           ;
         { "DELETED"  , "Deleted()", {|| client->( Deleted())},                 ;
           // CA's functional Implementation for a Conditional Index, Hmmm...
           {|| OrdCondSet("Deleted()", {|| client->( Deleted())},,,,,RecNo(),,,,)} }, .f.} ;
          }                                                 ;
        }                                                   ;
      }

Local oClientWA := TWAreaRDD():New( aCLIENT_DBF  , aCLIENT_CDX  )
// etc ...

Getting it all into Perspective
In this context; if an object is an independent instance of a
class, then a class defines the knowledge domain of that object.
The TBaseWArea class outlines the essential knowledge required to
navigate through any Work Area or Structured Data Storage Medium.
TWAreaDBF inherits all the TBaseWArea features but additionally
adds new and redefines other features to specifically handle
Database/Index manipulation.  This class was developed for the
5.0x series and failed to handle RDD's, hence the need to derive
a TWAreaRDD class.  Each class deals with a specific domain which
increases the flexibility to derive descendant classes.
Similarly, a browse class is not concerned with the storage
medium that it is traversing.  It's knowledge domain is to send
messages to the appropriate object in response to the requests
made within the browse.

Suppose we instance objects for each descendant Work Area class
as in Eg 4., but further instance a browse object of type
TStdBrowse.  The knowledge domain common to all four Work Area
object are the navigational features outlined in TBaseWArea.
Therefore, we can pass any of the Work Area objects to the browse
object to traverse.  Hence, a reasonable analogy would be to
examine the browse object as a vehicle, and the various Work Area
objects as interchangeable engines.


So, where do we go to from here?
The Work Area classes are an ideal hierarchy as they should
seemlessly port to Visual Objects.  There are many other DOS
class hierarchies which we can pursue including the extended
TBrowse, TDialog, and TMenu which combine to form a priceless
Application Framework Class Library.  Object Oriented Design is a
serious long term investment, it's the simplest concept with
undenied power.  Beware - Bad Habits (Procedural Design) Die
Hard!


                                        - Ashin S.Wimalajeewa
