State Patterns & C++ by Julian Macri Listing One /***** (State class definition) ****************/ class Account; class State { friend class Account; public: static State * InitialState(Account * account); static double CurrentMinimumBalance() { /* CODE THAT OBTAINS CURRENT MIN BALANCE FROM SOME DATABASE */ return CURRENT_MIN_BALANCE; }; private: Account * _context; double _balance; protected: Account * context() const {return _context;}; void context(Account * newAccount) {_context = newAccount;}; double balance() const {return _balance;}; void balance(double newBalance) {_balance = newBalance;}; virtual State * transitionState() = 0; public: virtual void deposit(double amount); virtual void withdraw(double amount); virtual void payInterest() = 0; public: State(Account * account, double balance) : _context(account), _balance(balance) {}; State(const State * source) : _context(source->context()), _balance(source->balance()) {}; }; Listing Two /************** (State class method code) ***************/ /************ class State **********************/ State * State::InitialState(Account * account) { return new NonInterestBearingState(account, 0.0); } void State::deposit(double amount) { this->balance(this->balance() + amount); this->transitionState(); } void State::withdraw(double amount) { this->balance(this->balance() - amount); this->transitionState(); } Listing Three /*************** (Concrete state classes) *************/ class InterestBearingState : public State { public: static double CurrentRate() { /* CODE THAT OBTAINS CURRENT RATE FROM SOME DATABASE */ return CURRENT_RATE; }; protected: virtual State * transitionState(); public: virtual void payInterest(); public: InterestBearingState(Account * account, double balance) : State(account, balance) {}; InterestBearingState(const State * source) : State(source) {}; }; class NonInterestBearingState : public State { public: static double CurrentTransactionFee() { /* CODE THAT OBTAINS CURRENT TRANSACTION FEE FROM SOME DATABASE */ return CURRENT_TRANSACTION_FEE; }; protected: virtual State * transitionState(); public: virtual void deposit(double amount); virtual void withdraw(double amount); virtual void payInterest(); public: NonInterestBearingState(Account * account, double balance) : State(account, balance) {}; NonInterestBearingState(const State * source) : State(source) {}; }; class OverdrawnState : public NonInterestBearingState { protected: void sendNoticeToAccountHolder() { /* PRINT OUT AND MAIL A NOTICE INDICATING ACCOUNT OVERDRAWN */ cout << "YOUR ACCOUNT IS OVERDRAWN" << endl; }; protected: virtual State * transitionState(); public: virtual void withdraw(double amount); public: OverdrawnState(Account * account, double balance) : NonInterestBearingState(account, balance) { this->sendNoticeToAccountHolder(); }; OverdrawnState(const State * source) : NonInterestBearingState(source) { this->sendNoticeToAccountHolder(); }; }; Listing Four /*************** (InterestBearingState methods) ********************/ State * InterestBearingState::transitionState() { if (this->context()->balance() < 0) this->context()->changeState(new OverdrawnState(this)); else if (this->context()->balance() < State::CurrentMinimumBalance()) this->context()->changeState(new NonInterestBearingState(this)); return this->context()->state(); } void InterestBearingState::payInterest() { this->balance(this->balance() * (1 + this->CurrentRate())); this->transitionState(); } Listing Five /****************** (NonInterestBearingState methods) *******************/ State * NonInterestBearingState::transitionState() { if (this->context()->balance() < 0) this->context()->changeState(new OverdrawnState(this)); else if (this->context()->balance() >= State::CurrentMinimumBalance()) this->context()->changeState(new InterestBearingState(this)); return this->context()->state(); } void NonInterestBearingState::payInterest() { /* PAY NO INTEREST AT ALL */ cout << "THIS ACCOUNT IS CURRENTLY NOT EARNING INTEREST" << endl; } void NonInterestBearingState::deposit(double amount) { /* Charge the transaction fee and then deposit */ this->balance(this->balance() - this->CurrentTransactionFee()); this->State::deposit(amount); cout << "A TRANSACTION FEE WAS CHARGED" << endl; } void NonInterestBearingState::withdraw(double amount) { /* Charge the transaction fee and then withdraw */ this->balance(this->balance() - this->CurrentTransactionFee()); this->State::withdraw(amount); cout << "A TRANSACTION FEE WAS CHARGED" << endl; } Listing Six /***************** (OverdrawnState methods) **************/ State * OverdrawnState::transitionState() { if (this->context()->balance() >= State::CurrentMinimumBalance()) this->context()->changeState(new InterestBearingState(this)); else if (this->context()->balance() >= 0) this->context()->changeState(new NonInterestBearingState(this)); return this->context()->state(); } void OverdrawnState::withdraw(double amount) { /* DO NOT ALLOW THEM TO WITHDRAW MORE MONEY */ cout << "YOU ARE NOT ALLOWED TO WITHDRAW FROM AN OVERDRAWN ACCOUNT" << endl; } 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 /************** (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 >> 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;}; }; 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(); }; }; Listing Twelve /**************** (Account methods) ***************/ #include "account.h" const double CURRENT_RATE = 0.065; const double CURRENT_MIN_BALANCE = 500.00; const double CURRENT_TRANSACTION_FEE = 1.00; double Account::CurrentRate() { /* CODE THAT OBTAINS CURRENT RATE FROM SOME DATABASE */ return CURRENT_RATE; } double Account::CurrentMinimumBalance() { /* CODE THAT OBTAINS CURRENT MIN BALANCE FROM SOME DATABASE */ return CURRENT_MIN_BALANCE; } double Account::CurrentTransactionFee() { /* CODE THAT OBTAINS CURRENT FEE FROM SOME DATABASE */ return CURRENT_TRANSACTION_FEE; } void Account::event(EVENT event) { switch (event) { case BALANCE_CHANGED : if (this->state()->requiresTransactionFee()) { /* Set the instance variable directly because*/ /* we don't want to trigger another event. */ _balance -= Account::CurrentTransactionFee(); cout << "A TRANSACTION FEE WAS CHARGED" << endl; } if (this->balance() < 0) { if (this->state()->isNotOverdrawn()) this->changeState(new OverdrawnState()); } else if (this->balance() < Account::CurrentMinimumBalance()) { if (this->state()->isInterestBearing() || this->state()->isOverdrawn()) this->changeState(new NonInterestBearingState()); } else { if (this->state()->isNotInterestBearing()) this->changeState(new InterestBearingState()); } break; case STATE_CHANGED : if (this->state()->isOverdrawn()) this->sendNoticeToAccountHolder(); break; } } 9