Home > Patterns and Practices > State Pattern

State Pattern

State Pattern

Problem:

How can we vary the behavior of an object when its state changes? Assume we have a bank account, one can withdraw or deposit money based on the account’s state. i.e one can withdraw money only if the account state is open. We can close the account only if the account is open. We can deposit money only if the account is open and so on. The behavior here changes with state. How should we model this?

 

Solution:

Attempt 1:
public class BankAccount
{
 private AccountStatus status;
 private Decimal balance;
 public void Withdraw(decimal amount)
 {
  switch (status)
  {
   case AccountStatus.Closed:
    throw new InvalidOperationException(“Can’t withdraw from a closed account”);
    break;
   case AccountStatus.Inactive:
    
    if(balance > amount + activationFee)
    {
     status = active;
     balance = balance – (amount + activationFee);
    }
    else
    {
     throw new InvalidOperationException(“Insufficient funds.”);
    }
break;
   case AccountStatus.Suspended:
    throw new InvalidOperationException(“Account has been suspended due to legal reasons.”);
    break;
   case AccountStatus.Active:
    if(balance > amount)
    {
     balance = balance – amount;
    }
    else
    {
     throw new InvalidOperationException(“Insufficient funds.”);
    }
    break;
   case AccountStatus.Open:
    throw new InvalidOperationException(“It will take 5 working days to activate your account ”);
    break;
   }
}
 }
 public void Deposit(decimal amount)
 {
  switch (status)
  {
   case AccountStatus.Closed:
    throw new InvalidOperationException(“Can’t deposit to a closed account”);
    break;
   case AccountStatus.Inactive:
    
    if((balance + amount) > activationFee)
    {
     status = active;
     balance = amount – activationFee;
    }
    else
    {
     throw new InvalidOperationException(“Your account is inactive. You must deposit atleast “ + activationFee.ToString() + “to activate it.”);
    }
    break;
   case AccountStatus.Suspended:
    throw new InvalidOperationException(“Account has been suspended due to legal reasons.”);
    break;
   case AccountStatus.Active:
    balance = balance + amount;
    break;
   case AccountStatus.Open:
    throw new InvalidOperationException(“It will take 5 working days to activate your account ”);
    break;
   }
}
 }
 public void Activate(decimal amount)
 {
  switch (status)
  {
   case AccountStatus.Closed:
    throw new InvalidOperationException(“Can’t activate a closed account”);
    break;
   case AccountStatus.Inactive:    
    if((amount + balance) > activationFee)
    {
     status = active;
     balance = amount – activationFee;
    }
    else
    {
     throw new InvalidOperationException(“You must deposit atleast “ + activationFee.ToString() + “to activate it.”);
    }
    break;
   case AccountStatus.Suspended:
    throw new InvalidOperationException(“Account has been suspended due to legal reasons.”);
    break;
   case AccountStatus.Active:
    throw new InvalidOperationException(“Your account is already active”);
   case AccountStatus.Open:
    if((amount + balance) > activationFee)
    {
     status = active;
     balance = amount – activationFee;
    }
    else
    {
     throw new InvalidOperationException(“You must deposit atleast “ + activationFee.ToString() + “to activate it.”);
    }
    break;
   }
}
 }
 
 // And so on for DeActivate / Close / Suspend RevokeSuspension etc.
}
You knew it was coming, yup the change. Assume the bank wants to introduce the feature of InstantCredit for its account holders.  With this the account holders can withdraw money till some limit even if the bank balance is less than zero. Meaning you have new state “OverDrawn” Oops the design suggested above is fragile as it doesn’t follow the OCP. How do we model this then? Welcome to the State pattern.

Attempt 2:

We’ll have one class per state of the object. Meaning we’ll have classes like
OpenAccount, ClosedAccount, ActiveAccount, InactiveAccount, SuspendedAccount and so on. All these accounts have the same abstract base class say AccountStateBase. Let the BankAccount class aggregate this AccountStateBase and delegate the calls to it. Let us jump into this design and if it helps us.

public class BankAccount //Context
{
 private AccountStateBase accountHandler;

 public BankAccount()
 {
  this. accountHandler = // based on the status create the corresponding state object.
 }

 public void Withdraw(decimal amount)
 {
  accountHandler.Withdraw(amount);
 }

 public void Deposit(decimal amount)
 {
  accountHandler.Deposit(amount);
 }

public void Activate(decimal amount)
 {
  accountHandler.Activate(amount);
 }
 public void ChangeState(AccountStateBase handler)
 {
  accountHandler = handler;
 }
 // and so on …
}

public abstract class AccountStateBase
{
 protected decimal balance;
 protected BankAccount account;
 public abstract void Withdraw(decimal amount);
 public abstract void Deposit(decimal amount);
 public abstract void Activate(decimal amount); 
 public AccountStateBase(BankAccount account, decimal balance)
 {
  this.account = account;
  this.balance = balance;
 }
 // And so on …
}

public class OpenAccount : AccountStateBase
{
 public OpenAccount(BankAccount account, decimal balance) : base(account, balance)
 {
 }
 public override void Withdraw(decimal amount)
 {
  throw new InvalidOperationException(“It will take 5 working days to activate your account ”);
 }
 public override void Deposit(decimal amount)
 {
  throw new InvalidOperationException(“It will take 5 working days to activate your account ”);
 }
 public override void Activate(decimal amount)
 {
  if((amount + balance) > activationFee)
  {
   balance = amount – activationFee;
   account.ChangeState(new ActiveAccount(account, balance));
  }
  else
  {
   throw new InvalidOperationException(“You must deposit atleast “ + activationFee.ToString() + “to activate it.”);
  }
 }
 // And so on …
}

public class ActiveAccount : AccountStateBase
{
 public OpenAccount(BankAccount account, decimal balance) : base(account, balance)
 {
 }

 public override void Withdraw(decimal amount)
 {
  if(amount < balance)
  {
   balance = balance – amount;
  }
  else
  {
   throw new InvalidOperationException(“Insufficient funds.”);
  }
 }
 public override void Deposit(decimal amount)
 {
  balance = balance + amount;
 }
 public override void Activate(decimal amount)
 {
  throw new InvalidOperationException(“Your account is already active”);  
 }
 // And so on …
}

Now adding a overdrawn account state is simple because we just add a new class inheriting from the Common state interface (AccountStateBase class in our case). The class diagram for the example and the the GoF Class structure diagram are as follows:


 


Advertisements
  1. Prakash
    December 20, 2005 at 12:08 pm

    Good one Sendhil.May be you could put the diagram first and then the code. It will help in understanding the code.Cheers!!!!:)

  2. Sendhil Kumar
    December 21, 2005 at 7:19 am

    I followed the no code approach in the Visitor notes. After all they are design patterns :-)If i give code, i’ll try to give the model first.Regards,Sendhil

  3. Unknown
    October 25, 2006 at 8:57 am

    Well a very nice example but i still see some coupling btwn the context & the various objects representing the specific state behaviour.I guess there needs a way to make state transistion a little more dynamic because when there are multiple condition’s to be checked for the state transistion to happen, you might see a switch case too.
     
    Again i am not sure if i need to aggregate or compose.:) since i see some state in these objects representing state specific behaviour

  4. Unknown
    October 25, 2006 at 8:58 am

    I personally feel the context must be ignorant abt the state transistion

  1. November 19, 2010 at 11:00 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s