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:

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.