Monday, May 30, 2011

State Pattern Usage and Sample in C#

The state pattern implementation enables objects to encapsulate state-based behavior in discrete classes. Responsibilities are identified and delegated to appropriate states so that the object alters its behavior when it’s internal state changes. A simple example to demonstrate would be the order processing scenario.
For e.g. A new order is created for a product and submitted for approval. The order can be approved or rejected based on the availability of the stock or other details. Once the order is approved, it is scheduled for shipment and the scheduled date is set for the order. On the date of shipment, the order is processed and shipped to the shipping address. On arrival of the shipment the order is received and closed for further processing.
During the entire process email notifications are sent to various departments or personnel. For e.g. when an order is created an email notification is send to the Purchase order dept. manager about the details of the order. When it is submitted for approval an email is send to the person who is responsible for approving the order, on rejection or approval of the order the person or dept. who created the order will be notified about that.
This would lead to lot of conditional logic in the code to process the emails and other details based on the current state of the order. Applying the state pattern makes it easier for factoring the conditional statements to polymorphic behavior by delegating the tasks to the abstract OrderState instance.

The class diagram for the solution looks like.

The source code for the sample:
OrderState.cs
public abstract class OrderState
{
    internal readonly Order CurrentOrder;

    protected OrderState(Order currentOrder)
    {
        CurrentOrder = currentOrder;
    }

    public virtual bool IsNew
    {
        get { return false; }
    }

    public virtual bool IsSubmitted
    {
        get { return false; }
    }

    public virtual bool IsApproved
    {
        get { return false; }
    }

    public virtual bool IsRejected
    {
        get { return false; }
    }

    public virtual bool IsScheduled
    {
        get { return false; }
    }

    public virtual bool IsShipped
    {
        get { return false; }
    }

    public virtual bool IsClosed
    {
        get { return false; }
    }

    public IEmailServer EmailServer {get { return CurrentOrder.EmailServer;}}

    public abstract void Process();
    public abstract void Cancel();
    public abstract bool SendEmailsOnStatusChange();
}

NewOrderState.cs
public class NewOrderState : OrderState
{
    public NewOrderState(Order order) : base(order)
    { }

    public override bool IsNew
    {
        get { return true; }
    }

    public override void Process()
    {
        var isSuccess = CurrentOrder.SendNotifications();
        if (isSuccess)
            CurrentOrder.ChangeState(new SubmittedOrderState(CurrentOrder));
    }

    public override void Cancel()
    {
        CurrentOrder.ChangeState(new ClosedOrderState(CurrentOrder));
    }

    public override bool SendEmailsOnStatusChange()
    {
        var emailAddresses = CurrentOrder.GetPurchaseOrderDeptContacts();
        return EmailServer.Send(emailAddresses);
    }
}

Order.cs
public class Order
{
    public Order()
    {
        _currentState = new NewOrderState(this);
    }

    public bool IsNew
    {
        get { return _currentState.IsNew; }
    }

    public bool IsSubmitted
    {
        get { return _currentState.IsSubmitted; }
    }

    public bool IsApproved
    {
        get { return _currentState.IsApproved; }
    }

    public bool IsRejected
    {
        get { return _currentState.IsRejected; }
    }

    public bool IsScheduled
    {
        get { return _currentState.IsScheduled; }
    }

    public bool IsShipped
    {
        get { return _currentState.IsShipped; }
    }

    public bool IsClosed
    {
        get { return _currentState.IsClosed; }
    }

    public IEmailServer EmailServer { get; set; }

    public bool SendNotifications()
    {
        return _currentState.SendEmailsOnStatusChange();
    }

    public void ChangeState(OrderState orderState)
    {
        _currentState = orderState;
    }

    public void Process()
    {
        _currentState.Process();
    }

    public OrderState GetState()
    {
        return _currentState;
    }

    private OrderState _currentState;

    public IEnumerable<string> GetPurchaseOrderDeptContacts()
    {
        return new List<string> {"purchaseOrderDept@abcfirm.org"};
    }

    public IEnumerable<string> GetApproverEmailAddress()
    {
        return new List<string> {"approverEmail@abcFirm.org" };
    }
}

Testing the order sample
[TestMethod]
public void WhenNewOrderIsCreatedStateShouldBeNewTest()
{
    var target = new Order();
    Assert.IsTrue(target.IsNew);
}

[TestMethod]
public void WhenNewOrderIsProcessedShouldSendEmailsAndChangeStatusToSubmitted()
{
    var target = new Order {EmailServer = GetMockedEmailServer()};
    target.Process();
    Assert.IsTrue(target.IsSubmitted);
}

private IEmailServer GetMockedEmailServer()
{
    var mockedServer = new Mock<IEmailServer>();
    mockedServer.Setup((x) => x.Send(It.IsAny<List<string>>())).Returns(true);
    return mockedServer.Object;
}

2 comments:

Anonymous said...

Nice Explanation...

Thanks

Anonymous said...

Nice Explanations...
Thanks