Design Patterns Series 15 - State Pattern

This Post will be about The State Pattern where an object keeps track of its internal state, and can change its behavior based on that state.

The State Pattern :

The State Design Pattern will "Allow an object to alter its behavior when its internal state changes. The object will appear to change its class." In other words your code should keep track of an internal state. Any part of the code can check what the current state is and react accordingly.

When you use the State Pattern, any part of your code can check what state is current. That clarify and centralize the workings of very large pieces of code because you have control over what far-flung code segments do, just by changing the current state.

In general, The State Pattern is useful when you have got a lot of code that's getting more and more murky and complex. If you can compartmentalize the working of what you are trying to do into a set of independent states, you can compartmentalize your code.

To use State Pattern in your code, instead of giving each state its own constant, it's better idea to give each state its own class. That way, you can call all methods on a state object anywhere in your code. All you have to do is load the correct state object into a generic variable and call various methods of that variable.

To imagine how the State Pattern can help us and when we can use it, Assume we want to make a system for car renting agency. The renting process contains four steps(states), Request a car, Check available cars, Make a rent, and give the car to the client. So the action will be taken depending on which step(state) of the renting process.

To Start building our system using State Pattern we will create an abstract class(RentCar) with all methods we need in renting process.

RentCar class
public abstract class RentCar
{
    public abstract void RequestCar();
    public abstract void CheckRequest();
    public abstract void CarRental();

    public abstract void SetState(State state);

    public abstract State GetWaitingState();
    public abstract State GetRequestCarState();
    public abstract State GetCarRentedState();
    public abstract State GetNoAvailableCarsState();

    public Int32 AvailableCars { get; set; }
}
The first three methods for renting process, SetState() method for changing the current state, the next four methods to get corresponding state object, and the AvailableCars property to hold the number of available car in the agency.

Now it's time to create the renting process itself by create a class that inherit RentCar class and implement its methods.

Rent Class
public class Rent : RentCar
{
    private State waitingState;
    private State requestCarState;
    private State carRentedState;
    private State noAvailableCarsState;
    private State myState;

    public Rent(Int32 carsCount)
    {
        AvailableCars = carsCount;
        waitingState = new WaitingState(this);
        requestCarState = new RequestCarState(this);
        carRentedState = new CarRentedState(this);
        noAvailableCarsState = new NoAvailableCarsState(this);
        myState = waitingState;
    }
}
Note the constructor accepts the number of availabe cars, instantiates four state objects, one for each state, and set the current state, myState, to WaitingState object.
public override void RequestCar()
{
    Console.WriteLine(myState.GotCarRequest());
}
public override void CheckRequest()
{
    Console.WriteLine(myState.CheckRequest());
}
public override void CarRental()
{
    Console.WriteLine(myState.CarRental());
    Console.WriteLine(myState.ReceiveCar());
}

public override void SetState(State state)
{
    myState = state;
}

public override State GetWaitingState()
{
    return waitingState;
}
public override State GetRequestCarState()
{
    return requestCarState;
}
public override State GetCarRentedState()
{
    return carRentedState;
}
public override State GetNoAvailableCarsState()
{
    return noAvailableCarsState;
}
We override methods of RentCar class and implements all required methods to complete the rental process, setting current state, and getting current state of the Rent object.
As we mentioned earlier,  it's better to give each state its own class. We will create an abstract class, State, to be the base for all states objects.

State Class
public abstract class State
{
    protected State(RentCar rentcar)
    {
        RentCar = rentcar;
    }
    public abstract String GotCarRequest();
    public abstract String CheckRequest();
    public abstract String CarRental();
    public abstract String ReceiveCar();

    public RentCar RentCar { get; set; }
}
The constructor accepts an instance of RentCar that its state changes and we have four methods for four different states. The implementation of these four methods will differ from one state to another, so We will create four classes for our four states.

WaitingState Class
public class WaitingState : State
{
    public WaitingState(RentCar rentcar)
        :base(rentcar)
    {
    }
    public override String GotCarRequest()
    {
        RentCar.SetState(RentCar.GetRequestCarState());
        return "We have recieved your request";
    }
    public override String CheckRequest()
    {
        return "You have to submit a request";
    }
    public override String CarRental()
    {
        return "You have to submit a request";
    }
    public override String ReceiveCar()
    {
        return "You have to submit a request";
    }
}
This class represents the waiting state of our object so the client can only make a request to rent a car, just he made that request we set the current state to RequestCarState. If the client called another method first, the system must refuse his action because the current state is WaitingState and the only allowed action is making a request.

The same thing with the other three states that will be represented by three classes, RequestCarState class, CarRentedState class, and NoAvailableCarsState class. You can refer to source code to see their implementation.

To test our car rental system, we just need to instantiate an instance of Rent class passing it the total number of available cars then call RequestCar()CheckRequest(), and CarRental() methods respectively.
Rent _rent = new Rent(10);
_rent.RequestCar();
_rent.CheckRequest();
_rent.CarRental();
The result we be according to the available cars in the agency(in the example we assumed the total available car is 10). If there is available cars to be rent the output will be
We have recieved your request
Your request was approved
Renting you a car...
Here are your car
If there is no available cars to be rent the output will be
We have recieved your request
Sorry, your request wasn't approved
You have to submit a request
You have to submit a request
You have simplified the code in the Rent dramatically by offloading it to State objects. The code in the Rent doesn't have to change to handle different states; only the State object itself changes. And each new State object has its own built-in methods to handle any requests made of it, appropriately, for the state it represents. By encapsulating how you handle each state in separate State objects, the code is easier to modify and maintain.

you can download the full source code here

Comments

Post a Comment

Popular posts from this blog

ASP.Net MVC : ActionLink with bootstrap icon

ASP.Net MVC : Conditional Validation using ValidationAttribute

Android : How to change progress bar color at runtime programmatically?