Nikhil Kamani
Nikhil Kamani

Reputation: 950

Why State Design Pattern over If-else

Everywhere I find people saying it is better to use state design pattern over if else. I want to know why:

  1. It is an improvement to if-else in real world use case?
  2. Does it make code more testable?

can any one please provide an example where state pattern actually improves the case, it is difficult to make sense of why i shall take the overhead of following state pattern when i can use if-else.

Examples over the internet is not enough to show how this pattern solves the real world problem or makes code more maintainable.

Upvotes: 3

Views: 4482

Answers (4)

DasElias
DasElias

Reputation: 569

One problem with if-else-clauses is, that they might get very long and that it is very hard to add another state because you have to change the code of many different classes. Imagine you want to develop a game with a main menu, with the game loop, and with a finished-screen. Without the State-pattern you would have to check the current state of the program at very different locations in the code like in the update-method or in the draw method. When you want to add a fourth state, for example a settings-screen, you would have to modify the code of many different classes, what is not ideal.

But with the State-pattern, you can solve this problem very elegantly. enter image description here

Upvotes: 0

R. Karlus
R. Karlus

Reputation: 2266

We will need to code in order to explain facts, but let's get some considerations first. The State Pattern allows you to create states of a flow and use let those states decide where to go and what to return. Ok, so let's suppose that you don't want to control the current state of an object every time you use it and change it's internal values. In this case your state can control itself for you and send you to the right place whenever you call it. The State pattern is very helpful when dealing with graphs, but I'll show you another example that I've seen in the GoF Design Patterns android app.

Consider the UML: enter image description here

We're goind to implement it.

public interface AtmState {
    void withdraw(int amount);
    void refill(int amount);
}

public class Working implements AtmState {

    Atm atm;

    Working(Atm atm) {
        this.atm = atm;
    }

    public void withdraw(int amount) {

        int cashStock = atm.getCashStock();
        if(amount > cashStock) {
            /* Insufficient fund.
            * Dispense the available cash */
            amount = cashStock;
            System.out.print("Partial amount ");
        }

        System.out.println(amount + "$ is dispensed");
        int newCashStock = cashStock - amount;
        atm.setCashStock(newCashStock);
        if(newCashStock == 0) {
            atm.setState(new NoCash(atm));
        }
    }

    public void refill(int amount) {
        System.out.println(amount + "$ is loaded");
        atm.setCashStock(atm.getCashStock()+amount);
    }
}

public class NoCash implements AtmState {

    Atm atm;

    NoCash(Atm atm) {
        this.atm = atm;
    }

    public void withdraw(int amount) {
        System.out.println("Out of cash");
    }

    public void refill(int amount) {
        System.out.println(amount + "$ is loaded");
        atm.setState(new Working(atm));
        atm.setCashStock(atm.getCashStock()+amount);
    }
}

At this point we have defined two states that interact with each other, they "know" when to change from itself to the other state, so you don't need to create a controller to handle when to change the object state, they already know when to change. Now let's get our Atm implementation:

public class Atm implements AtmState {
    int cashStock;
    AtmState currentState;

    public Atm() {
        currentState = new NoCash(this);
    }

    public int getCashStock() {
        return cashStock;
    }

    public void setCashStock(int CashStock) {
        this.cashStock = CashStock;
    }

    public void setState(AtmState state) {
        currentState = state;
    }

    public AtmState getState() {
        return currentState;
    }

    public void withdraw(int amount) {
        currentState.withdraw(amount);
    }

    public void refill(int amount) {
        currentState.refill(amount);
    }
}

Ok, now we have two statements of an object and one Atm implementation. Now we can test it separately so we can write tests only for the NoCash state as we can do it for the Working state. It is more granulate as you can see. And here we have our client code:

public class StateClient {

    public static void main(String [] args) {
        Atm atm = new Atm();
        atm.refill(100);
        atm.withdraw(50);
        atm.withdraw(30);
        atm.withdraw(30); // overdraft
        atm.withdraw(20); // overdraft
        atm.refill(50);
        atm.withdraw(50);
    }
}

Output:

100$ is loaded
50$ is dispensed
30$ is dispensed
Partial amount 20$ is dispensed
Out of cash
50$ is loaded
50$ is dispensed

Note that we don't need to handle the state of our ATM and we can even test it easily. Besides you didn't write if-else statements into your client code, you have already writed it into the states itself where it should be, because your state needs to know when to check or not itself but not your client. Your client just need to get the correct answer for any call.

As I said before, testing this is much easier because now you can have separate and small tests for any state. Your logic is distributed in places where it make sense and your code get really easier to understand.

Hope I could help you.

Upvotes: 7

YK S
YK S

Reputation: 3430

It is an improvement to if-else in real world use case ?

If your if-else involves simple business logic then it doesn't make sense to use the state-design pattern. But if you have complex business logic then it is probably better to sue state-design pattern as it will help you in decoupling the code and thus help in maintenance. Also if want to add new state no need to go to the class just create a new class and at runtime you can inject it.

Does it make code more testable ?

Since the state corresponds to different classes, they are not hard coded and thus you can inject them at runtime which means they can be mocked as per you test case. Thus easier unit-testing.

Upvotes: 0

Bryan
Bryan

Reputation: 186

1) As always, any general "is it better?" type question can be a matter of opinion or otherwise answered with "It depends." One example of a real-world use case would be having a single-instance object which is a control for multiple other processes or threads. The processes can poll the object for its state and process their specific logic based upon that.

Consider a parking garage. You may have a state machine which, when polled, tells the polling process that the garage is either full or not full, and maybe a time of day which it expects to change from full to not full, or from not full to full based upon some factors (reservations, for example). Then you have other processes which monitor the inbound traffic, outbound traffic, a monitor at the entrance telling potential customers whether there is space to park, a parking reservation system, seasonal/semester/annual passes, etc. Each of these things needs to know, independent of all the others, whether the garage is full.

2) Any object where you can explicitly set a state is easily testable. Compare this to mocking some logic to get to a state you want to test, the latter tends to be more (human) error-prone.

Upvotes: 0

Related Questions