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++


An event trigger and handling scheme is used to determine state transitions. Two possible events can happen to an Account object: Its balance can change and its state can change. The setter for the balance sets the _balance instance variable, then triggers the BALANCE_CHANGED event. Similarly, the setter for the state sets the _state instance variable and then triggers the STATE_CHANGED event. In the event(event) method in Listing Twelve, different behaviors and state transitions can occur as the result of an event. If the balance has changed, then the current state may be asked some questions. For example, the state is asked if a transaction fee is required. If so, then the transaction fee is deducted from the balance (the instance variable is directly modified; otherwise, another BALANCE_CHANGED event would be triggered) and users are notified. Next, the state transition rules come into effect. Depending on the current balance and the current state's responses to various checker methods, the account changes to a new state. A STATE_CHANGED event is handled by asking the state object if the account is overdrawn. If so, users are notified of this. This will only occur when the account's state changes to OverdrawnState, which is the desired behavior.

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


Once again, refer to Listing Eight for the ATM main program. Executing this code using the check implementation will result in the same behavior and output as for the delegation implementation.

State as Singleton

Performance overhead can be a concern for both implementations. If a context object can go through many state transitions, then a run-time overhead will be paid because of the construction and destruction of state objects. One solution is to make the various state classes Singletons. In the checker implementation, this is simple because none of the state classes contain instance variables. In the delegation implementation, Account would have to keep its own _balance instance variable. State would have no instance variables. Each method that the state hierarchy has would require an extra parameter, where the context object could pass itself in by reference (deposit(amount, context), withdraw(amount, context), and payInterest(context), for instance); thereby eliminating the need for the _context instance variable.

Conclusion

Both approaches to the State design patterns have their pros and cons. In the delegation implementation, the state class hierarchy contains and implements the state-dependent attributes and methods of its context class. This usually results in the context class being very thin. The state hierarchy also contains and implements the rules for any state transitions. The checker implementation instead keeps the bulk of the code in the context class.

The delegation implementation results in slightly more code to maintain but, in terms of complexity, it is a matter of opinion which is more complex. The checker implementation keeps all the code for any one method in one place, thereby allowing a programmer to concisely see all the different code paths for that method. The delegation implementation, on the other hand, spreads code across multiple classes. Although it is more difficult to see all the various code paths, it makes it easier to understand each individual code path because they aren't cluttered by large groups of if-then-else constructs. My recommendation is to use the checker implementation for classes that contain a small set of states. If you design classes that can go through many different possible states and, therefore, could have many different state-dependent behaviors, the delegation implementation is probably the better way to go.


Julian develops Java applications to support securities trading. He can be contacted at [email protected].


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.