Wednesday, June 22, 2011

Resharper series - Replacing state altering conditionals with state pattern

If your code has some complex state altering conditional logic, you can refactor that code to use a State pattern implementation by replacing the type code with subclasses. For e.g the Order class given below has the OrderState implemented as a string. The state of the class is changed on some conditional logic.
public class Order
{
    private const string NEW = "New";
    private const string SUBMITTED = "Submitted";
    private const string APPROVED = "Approved";
    private const string INPROGRESS = "InProcess";
    private const string COMPLETED = "Completed";

    private string _orderState;

    public Order()
    {
        _orderState = NEW;
    }

    public string GetState()
    {
        return _orderState;
    }

    public void Submitted()
    {
        if(_orderState.Equals(NEW)) _orderState = SUBMITTED;
    }

    public void Approved()
    {
        if(_orderState.Equals(SUBMITTED)) _orderState = APPROVED;
    }

    public void Processing()
    {
        if(_orderState.Equals(APPROVED)) _orderState = INPROGRESS;
    }

    public void Completed()
    {
        if(_orderState.Equals(INPROGRESS)) _orderState = COMPLETED;
    }
}

We can refactor this to State pattern to have States implemented as classes.
First step of refactoring involves changing OrderState to a type-safe entity by making it a class rather than having it as string instances. We use the Encapsulate fields refactoring pattern to encapsulate the _orderState variable.




After encapsulating fields for _orderState the code now looks as.
public void Submitted()
{
    if(GetOrderState.Equals(NEW)) SetOrderState = SUBMITTED;
}

public void Approved()
{
    if(GetOrderState.Equals(SUBMITTED)) SetOrderState = APPROVED;
}

public void Processing()
{
    if(GetOrderState.Equals(APPROVED)) SetOrderState = INPROGRESS;
}

public void Completed()
{
    if(GetOrderState.Equals(INPROGRESS)) SetOrderState = COMPLETED;
}

Next we create new classes for the OrderState hierarchy and use that instead of the strings an use the Extract superclass pattern to create a superclass OrderState for these classes.





public class NewOrder : OrderState
{
}

Next we need to change the _orderStatus type to OrderState in the Order class from string. First we apply the Extract method pattern to the const string values as given below




After extract method is applied, we need to change the signature of these methods to use the OrderState instead of string. The Change Signature refactoring can be used to do this.





After applying these refactoring the code looks like
private static readonly OrderState NEW = GetNewStatus();

private static OrderState GetNewStatus()
{
    return new NewOrder();
}

Do the same for other states also            
The final version of our Order class now looks like.
public class Order
{
    private static readonly OrderState NEW = GetNewStatus();

    private static OrderState GetNewStatus()
    {
        return new NewOrder();
    }

    private static readonly OrderState SUBMITTED = GetSubmittedStatus();

    private static OrderState GetSubmittedStatus()
    {
        return new SubmittedOrder();
    }

    private static readonly OrderState APPROVED = GetApprovedState();

    private static OrderState GetApprovedState()
    {
        return new ApprovedOrder();
    }

    private static readonly OrderState INPROGRESS = GetInProgressState();

    private static OrderState GetInProgressState()
    {
        return new InProgressOrder();
    }

    private static readonly OrderState COMPLETED = GetCompletedState();

    private static OrderState GetCompletedState()
    {
        return new CompletedOrder();
    }

    private OrderState _orderState;

    public Order()
    {
        SetOrderState = NEW;
    }

    public OrderState GetOrderState
    {
        get { return _orderState; }
    }

    public OrderState SetOrderState
    {
        set { _orderState = value; }
    }

    public OrderState GetState()
    {
        return GetOrderState;
    }

    public void Submitted()
    {
        if(GetOrderState.Equals(NEW)) SetOrderState = SUBMITTED;
    }

    public void Approved()
    {
        if(GetOrderState.Equals(SUBMITTED)) SetOrderState = APPROVED;
    }

    public void Processing()
    {
        if(GetOrderState.Equals(APPROVED)) SetOrderState = INPROGRESS;
    }

    public void Completed()
    {
        if(GetOrderState.Equals(INPROGRESS)) SetOrderState = COMPLETED;
    }
}


2 comments:

Magesh said...

Nice post!! Why dont you have a RSS link for your posts?? I see a "atom" link for the comments, but none of for the posts... or am i missing something??

Prajeesh Prathap said...

Mahesh,
Just added one. Thanks for visiting