|
When transitioning to a new development system, it is often necessary to support
a legacy system - databases, processes, routines, etc.
The problem is that many programmers scatter legacy support throughout their
system, and this makes obsoleting legacy support a very error prone process.
Yet object-orientation, and, especially C++ Builder's component architecture,
can make it possible to avoid all of these problems.
For the sake of this article, let's consider a simple system. In this system,
there is a legacy database consisting of an old Account table and a new Account
table, which has additional fields. The old Account table needs to be maintained
to support some legacy programming which uses it.
First, we define a new class and base it on TDBDataSet. Thus, it can be dropped
into a DataModule, it exports the functions and properties of a data set, and
once the legacy facilities are obsolete, it can be simply be slightly altered
(mostly by removing code) to be no more than the current database account table.
Such a change can occur without requiring any external code changes.
This approach requires overriding many or all of the functions and properties
of the TDBDataset, not all of which are shown in this example.
class Account: public TDBDataSet
{
public:
__fastcall Account(void);
__fastcall ~Account(void);
bool __fastcall FindFirst(void);
bool __fastcall FindNext(void);
// ... any other TDataSet methods
to be overriden
// Field interfaces
__property AnsiString ID = {read=GetID,write=SetID};
__property int OrderCount = {read=GetOrderCount,write=SetOrderCount};
__property int AccountType = {read=GetAccountType,write=SetAccountType};
// ... etc.
private:
// Get and set function interfaces...
//
TQuery *myQuery;
TField *myCurrentID;
TField *myLegacyID;
TField *myCurrentOrderCount;
TField *myLegacyOrderCount;
TField *myCurrentAccountType;
} |
The implementation is where the rubber meets the road. We will assume that
the ID fields are compatible, and that none of the fields in the two databases
have the same name. We will assume that both databases are accessible through
the BDE (Borland Database Engine), either with ODBC or native drivers. Thus,
we can use the BDE's support for joining across databases (heterogenous joins)
to make our job easier.
__fastcall Account::Account(void)
{
myQuery = new TQuery;
myQuery->RequestLive = TRUE;
// Observe the heterogenous join notation for the
TQuery
// :dbname:tablename gives the name of the database
followed by the name of the table
// SQL requires surrounding quotes because of the
":", and those must be escaped with "\"
// The name after the database/table name is a name
that can be used in the WHERE to distinguish fields from each database
myQuery->SQL->Add("SELECT * FROM \":Current:Account\"
Current,\":Legacy:Account\" Legacy WHERE Current.ID = Legacy.Acctnum");
myQuery->Open();
// Note the caching of the TFields, which improves
performance
myCurrentID = myQuery->FieldByName("ID");
myLegacyID = myQuery->FieldByName("Acctnum");
myCurrentOrderCount = myQuery->FieldByName("OrderCount");
myLegacyOrderCount = myQuery->FieldByName("ORDCOUNT");
myCurrentAccountType = myQuery->FieldByName("AccountType");
}
...
bool __fastcall Account::FindFirst(void)
{
return myQuery->FindFirst();
}
...
AnsiString __fastcall Account::GetOrderCount(void)
{
return myCurrentOrderCount->AsString;
}
...
void __fastcall Account::SetOrderCount(int theOrderCount)
{
CurrentOrderCount->AsInteger = theOrderCount;
LegacyOrderCount->AsInteger = theOrderCount;
}
...
|
Here you can see how the Account object encapsulates the updates and control
over the legacy and current databases in a single object, hiding the presence
of the twin tables, and allowing you the freedom to later eliminate the legacy
support with no consequences for the rest of your system.
|
|