19 Mar
2010

Don’t Give Up on the State Pattern Just Yet

Category:UncategorizedTag: :

Last week, Greg Young wrote a blog post about State Pattern Misuse. In this post he talks about how ugly the State pattern can become if some operations are not allowed when the context object is in a particular state. The solution he proposes is to use a separate domain class for each state in the model so that it contributes to the Ubiquitous Language.

Just to be clear, I agree and value this approach when it makes sense in the domain model. However, I do not like to entirely give up on the use of the State pattern either. We can eliminate some of the friction by using a very simple approach, namely role interfaces.

To show you a simple example, I took the code of one of the samples on the DoFactory web site and put in some refactoring. Please take a look at the original code before reading any further.

Now instead of having abstract methods on the State base class, I created three separate role interfaces:

public interface ICanDeposit
{
    void Deposit(Double amount);
}

public interface ICanWithdraw
{
    void Withdraw(Double amount);
}

public interface ICanPayInterest
{
    void PayInterest();   
}

 

Both the SilverState and the GoldState class implement all these interfaces, but the one we’re particularly interested in is the RedState class because only deposits should be allowed for this state.

class RedState : State, ICanDeposit
{
    private const Double UpperLimit = 0.0;

    public RedState(State state)
    {
        Balance = state.Balance;
        Account = state.Account;
    }

    public void Deposit(double amount)
    {
        Balance += amount;
        StateChangeCheck();
    }

    private void StateChangeCheck()
    {
        if(Balance > UpperLimit)
            Account.State = new SilverState(this);
    }
}

 

The RedState therefore only implements the ICanDeposit interface. Just for the record, the code of the State base class is now dramatically reduced.

abstract class State
{   
    public Account Account { get; protected set; }
    public Double Balance { get; protected set; }
}

 

With this setup we can use these role interfaces in the Account class to determine whether a particular operation is allowed for the current state.

public void Withdraw(double amount)
{
    // One can not always withdraw
    if(false == CanWithdraw())
    {
        // Should throw an exception or at least a towel
        Console.WriteLine("No funds available for withdrawal!");
        return;
    }

    var canWithdraw = (ICanWithdraw)State;
    canWithdraw.Withdraw(amount);
    
    ...
}

private Boolean CanWithdraw()
{
    return State is ICanWithdraw;
}

 

This way we’re able to eliminate all operations that don’t make sense for a particular state while still being able to determine all of its capabilities when needed.

Hope this helps.

Find me

RSS
Facebook
Twitter
LinkedIn
SOCIALICON
SOCIALICON

Disclaimer

The opinions and content expressed here are my own and not those of my employer.