How does the account fit into all of this? Listing Seven shows the definition for the Account
class. The methods deposit(amount)
, withdraw(amount)
, payInterest()
, and balance()
all delegate their implementations to the account's current state object. Depending on the current state object, these functions will behave differently. As you can see, the Account
class is very light in code. Its largest function is the changeState(newState)
method which cleanly sets its _state
instance variable while deleting the previous state object.
Listing Seven
/**************** (Account class definition) **********/ #include "state.h" class Account { friend class State; friend class InterestBearingState; friend class NonInterestBearingState; friend class OverdrawnState; private: State * _state; protected: State * state() const {return _state;}; void state(State * newState) {_state = newState;}; void changeState(State * newState) { if (newState != this->state()) { delete this->state(); this->state(newState); } }; public: double balance() { return this->state()->balance(); }; void deposit(double amount) { this->state()->deposit(amount); }; void withdraw(double amount) { this->state()->withdraw(amount); }; void payInterest() { this->state()->payInterest(); }; public: Account() : _state(State::InitialState(this)) {}; virtual ~Account() { delete this->state(); }; };
Listing Eight is a program that creates an account
object and allows a user to make deposits, withdrawals, and to ask for interest payment. Listing Nine is sample output of this program.
Listing Eight
/************** (atm.C) ************/ #include "account.h" #include "iostream.h" #include "iomanip.h" int main() { char option; double amount; int quit = 0; Account account; cout << "WELCOME TO JULIAN'S BANK" << endl; cout << "========================" << endl; while (!quit) { cout << endl << "Do you want to (d)eposit,(w)ithdraw, earn (i)nterest, or (q)uit? " << flush; cin >> option; cout << setiosflags(ios::fixed) << setprecision(2); switch (option) { case 'd' : case 'D' : cout << "Deposit amount = " << flush; cin >> amount; account.deposit(amount); cout << "Balance = $" << account.balance() << endl; break; case 'w' : case 'W' : cout << "Withdrawal amount = " << flush; cin >> amount; account.withdraw(amount); cout << "Balance = $" << account.balance() << endl; break; case 'i' : case 'I' : account.payInterest(); cout << "Balance = $" << account.balance() << endl; break; case 'q' : case 'Q' : quit = 1; break; } } return 0; }
Listing Nine
/***************** (Program output) ******************/ [ric1:macri]/u/macri/article/code1 >> atm WELCOME TO JULIAN'S BANK ======================== Do you want to (d)eposit,(w)ithdraw, earn (i)nterest, or (q)uit? d Deposit amount = 1000 A TRANSACTION FEE WAS CHARGED Balance = $999.00 Do you want to (d)eposit,(w)ithdraw, earn (i)nterest, or (q)uit? i Balance = $1063.94 Do you want to (d)eposit,(w)ithdraw, earn (i)nterest, or (q)uit? d Deposit amount = 500 Balance = $1563.94 Do you want to (d)eposit,(w)ithdraw, earn (i)nterest, or (q)uit? w Withdrawal amount = 1250 Balance = $313.93 Do you want to (d)eposit,(w)ithdraw, earn (i)nterest, or (q)uit? i THIS ACCOUNT IS CURRENTLY NOT EARNING INTEREST Balance = $313.93 Do you want to (d)eposit,(w)ithdraw, earn (i)nterest, or (q)uit? w Withdrawal amount = 500 YOUR ACCOUNT IS OVERDRAWN A TRANSACTION FEE WAS CHARGED Balance = $-187.07 Do you want to (d)eposit,(w)ithdraw, earn (i)nterest, or (q)uit? w Withdrawal amount = 200 YOU ARE NOT ALLOWED TO WITHDRAW FROM AN OVERDRAWN ACCOUNT Balance = $-187.07 Do you want to (d)eposit,(w)ithdraw, earn (i)nterest, or (q)uit? d Deposit amount = 1000 A TRANSACTION FEE WAS CHARGED Balance = $811.94 Do you want to (d)eposit,(w)ithdraw, earn (i)nterest, or (q)uit? q [ric1:macri]/u/macri/article/code1 >>
The Checker Implementation
In the checker implementation, the context
class still contains all the main logic, while its state hierarchy contains very thin classes that respond to various checker methods. In this example, State
and its derived classes (see Figure 2 and Listing Ten) all respond to isInterestBearing()
, isNotInterestBearing()
, isOverdrawn()
, isNotOverdrawn()
, requiresTransactionFee()
, and doesNotRequireTransactionFee()
. The positive methods (isInterestBearing()
, isOverdrawn()
, and requiresTransactionFee()
) are declared as pure virtual functions in State
. The negative methods (isNotInterestBearing()
, isNotOverdrawn()
, and doesNotRequireTransactionFee()
), provided for readability, simply return the Boolean opposite of the positive methods.
Figure 2: The checker implementation.
Listing Ten
/****************** (State class definition) *********************/ class State { public: static State * InitialState(); public: virtual int isOverdrawn() = 0; int isNotOverdrawn() {return !this->isOverdrawn();}; virtual int isInterestBearing() = 0; int isNotInterestBearing() {return !this->isInterestBearing();}; virtual int requiresTransactionFee() = 0; int doesNotRequireTransactionFee() {return !this->requiresTransactionFee();}; }; class InterestBearingState : public State { public: virtual int isOverdrawn() {return 0;}; virtual int isInterestBearing() {return 1;}; virtual int requiresTransactionFee() {return 0;}; }; class NonInterestBearingState : public State { public: virtual int isOverdrawn() {return 0;}; virtual int isInterestBearing() {return 0;}; virtual int requiresTransactionFee() {return 1;}; }; class OverdrawnState : public NonInterestBearingState { public: virtual int isOverdrawn() {return 1;}; };
Once the state hierarchy and its checker methods have been implemented, the Account
class can be developed. Listing Eleven contains the definition for the class. You will see that withdraw(amount)
and payInterest()
ask its current state checker questions to determine how to behave. For example, in payInterest()
, if the state is interest bearing, then interest is paid; otherwise, the message is displayed to users indicating that it is a noninterest-bearing account. Similarly, the withdraw(amount)
method checks its state to determine if the account is overdrawn.
Listing Eleven
/***************** (Account class definition) *****************/ #include "state.h" #include "iostream.h" class Account { public: static double CurrentRate(); static double CurrentMinimumBalance(); static double CurrentTransactionFee(); protected: enum EVENT { BALANCE_CHANGED, STATE_CHANGED }; private: double _balance; State * _state; protected: void event(EVENT event); State * state() const {return _state;}; void state(State * newState) { _state = newState; this->event(STATE_CHANGED); }; void changeState(State * newState) { if (newState != this->state()) { delete this->state(); this->state(newState); } }; void balance(double newBalance) { _balance = newBalance; this->event(BALANCE_CHANGED); }; void sendNoticeToAccountHolder() { /* PRINT OUT AND MAIL A NOTICE INDICATING ACCOUNT OVERDRAWN */ cout << "YOUR ACCOUNT IS OVERDRAWN" << endl; }; public: double balance() { return _balance; }; void deposit(double amount) { this->balance(this->balance() + amount); }; void withdraw(double amount) { if (this->state()->isNotOverdrawn()) this->balance(this->balance() - amount); else cout << "YOU ARE NOT ALLOWED TO WITHDRAW FROM AN OVERDRAWN ACCOUNT" << endl; }; void payInterest() { if (this->state()->isInterestBearing()) this->balance(this->balance() * (1 + Account::CurrentRate())); else cout << "THIS ACCOUNT IS CURRENTLY NOT EARNING INTEREST" << endl; }; public: Account() : _state(State::InitialState()), _balance(0) {}; virtual ~Account() { delete this->state(); }; };