As promised in the bank header page , we define all of the actual classes in the cpp file. This allows us to change the set of classes without the main program being aware of the changes. Since the different types of accounts is only known in this file, the enumerated values used in the functions are listed asa file scope type. This means any function in this file may use the enumerated values, but no other file in the project knows of these values.
enum {SAVINGS,CHECKING};
class SavingsAccount :public Account
{
public:
SavingsAccount();
SavingsAccount(int id,double amount);
virtual void write(ostream & );
virtual void print(ostream&);
private:
double computeInterest();
virtual Account* clone(int=0,double=0);
static double _interestRate;
};
class CheckingAccount :public Account
{
public:
CheckingAccount();
CheckingAccount(int, double);
virtual void write(ostream & );
virtual void print(ostream &);
private:
virtual Account* clone(int=0,double=0);
};
Before you can read objects from a file with more than one type of objects your file must be created in an appropriate way. In the case of the accounts, when an item writes itself, the first thing it writes is its type.
void SavingsAccount::write(ostream &out )
{
out << SAVINGS << endl; // write out the type of item
Account::write(out);
}
void CheckingAccount::write(ostream & out )
{
out < // write out the type of item
Account::write(out);
}
Each of these functions calls on Account::write since the class Account contains the rest of the data for each type of account.
The function that writes to the array uses an iterator and calls on each item to write itself.
void AccountArray::write()
{
ofstream out("Accounts.dat");
ArrayIterator < AcctPtr > next(accts); //Create the iterator
out << accts.numberInArray() << endl; //write the number of items to the file
while (next) // loop until the last item in the list
{
next()->write(out); //let the item send itself to the file
out << endl; // each object begins on a new line
next++; // get the next item in the list
}
}
Once a file has been created in this format, it is easy to read the file using the create function to clone the appropriate type of object.
void AccountArray::read()
{
ifstream in("Accounts.dat");
int size;
in>>size; //first thing in file is the number of items
for (int i = 0;i < size;i++)
{
int whichType;
in >> whichType; // This is the subscript of the type listed above
AcctPtr nextOne = Account::create(whichType); // actually create the object
nextOne->read(in); // let it read itself
accts.insertItem(nextOne); // Insert it in the array
}
}
The create function is as you would expect a factory
Account* Account::create(int i,int acctId, double balance)
{
Account *type[]={&SavingsAccount(),&CheckingAccount()};
if (i < 2 && i >=0)
return type[i]->clone(acctId,balance);
else
return NULL;
}
You also use the abstract factory in create new account function. The account dialog has a TComboBox that lists all types of accounts. The number of the choice from this box, coinsides with the number passed to the abstract factor.
Once the dialog is displayed and the user enters the data, the factory builds the correct item to be entered.
void __fastcall TSDIAppForm::NewAccount1Click(TObject *Sender)
{
AccountDlg->AccountTypes->Items->Text = Account::listAccountTypes(); //Directly transfer possible choices to the combo box
AccountDlg->AccountTypes->ItemIndex = 0;
AccountDlg->AccountId->Text ="";
AccountDlg->Balance->Text ="";
if (AccountDlg->ShowModal())
{
AcctPtr p = Account::create(AccountDlg->AccountTypes->ItemIndex,
AccountDlg->AccountId->Text.ToInt(),
AccountDlg->Balance->Text.ToDouble());
_accounts.insertItem(p);
Invalidate();
}
}
The static function that creates the account types list is a simple function that returns a string with the return character between each choice.
String Account::listAccountTypes()
{
return "Savings\nChecking";
//These must be in the same order as the listing
// of pointers in create, and same order as the
// enum type in class definition
}
The code you can download, has not checks to see if the values in the AccountId and the Balance are the right kind (int and double respectively). You may also want to make sure that each field has length > 0. This can all be done in the OnChange event hanlder. To get the whole project click here.