I have a bank account program. Each account can be in Initial State or Trusted Account State (at present). In future new states may be added. Interest is not paid if it is in initial state. But 9% interest is paid if it is in Trusted Account State.
The following code works. But there is a tight coupling in the ChangeState() method. The InitialAccountState
needs to know about the existence of TrustedAccountedState
.. And if we add a new state called VeteranAccountedState
, the ChangeState() method needs to be re-written in InitialAccountState
class.
What is the best .Net 4.0 way to reduce this coupling?
State Pattern
The state pattern allows a object's state to change at any given moment which actually alters its behavior.
Coupling is not a problem in the State Pattern when the order of change of the states is pre-defined. For example, a traffic light will always change from Green—Yellow—Red. In this scenario coupling is not a problem – Green is sure that it is always going to be Yellow state in the next step. Refer Analysis Of State Machine Pattern
ABSTRACT State
abstract class AccountState
{
// Properties
public BankAccount Account { get; set; }
public double Balance { get; set; }
protected double interest;
protected double lowerLimit;
protected double upperLimit;
public abstract void Deposit(double amount);
public abstract void PayInterest();
}
CONCRETE
class InitialAccountState : AccountState
{
public InitialAccountState(AccountState state) :this(state.Balance, state.Account)
{
}
public InitialAccountState(double balance, BankAccount account)
{
this.Balance = balance;
this.Account = account;
Initialize();
}
private void Initialize()
{
lowerLimit = 0.0;
upperLimit = 1000.0;
}
public override void Deposit(double amount)
{
Balance += amount;
ChangeState();
}
public override void PayInterest()
{
throw new Exception("No Interest Allowed");
}
private void ChangeState()
{
if (Balance > upperLimit)
{
Account.State = new TrustedAccountedState(this);
}
}
}
class TrustedAccountedState : AccountState
{
public TrustedAccountedState(AccountState state): this(state.Balance, state.Account)
{
}
public TrustedAccountedState(double balance, BankAccount account)
{
this.Balance = balance;
this.Account = account;
Initialize();
}
private void Initialize()
{
interest = 0.05;
lowerLimit = 1000.0;
upperLimit = 10000000.0;
}
public override void Deposit(double amount)
{
Balance += amount;
ChangeState();
}
public override void PayInterest()
{
Balance += interest * Balance;
ChangeState();
}
private void ChangeState()
{
if (Balance < lowerLimit)
{
Account.State = new InitialAccountState(this);
}
}
}
CONTEXT
class BankAccount
{
// Properties
public AccountState State { get; set; }
public double Balance
{
get { return State.Balance; }
}
// Constructor
public BankAccount(string owner)
{
this.State = new InitialAccountState(0.0, this);
}
public void Deposit(double amount)
{
State.Deposit(amount);
Console.WriteLine("Deposited {0:C} --- ", amount);
Console.WriteLine(" Balance = {0:C}", this.Balance);
Console.WriteLine(" Status = {0}", this.State.GetType().Name);
Console.WriteLine("");
}
public void PayInterest()
{
State.PayInterest();
Console.WriteLine("INTEREST PAID --- ");
Console.WriteLine(" Balance = {0:C}", this.Balance);
Console.WriteLine(" Status = {0}\n", this.State.GetType().Name);
}
}
CLIENT
class Program
{
static void Main(string[] args)
{
BankAccount account = new BankAccount("Jim Johnson");
account.Deposit(500.0);
account.Deposit(300.0);
account.Deposit(550.0);
account.PayInterest();
Console.ReadKey();
}
}
REFERENCES