Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

State Patterns & C++


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();
    };
};


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.