Follow treslines by email clicking Here!

Tuesday, August 12, 2014

Programming Design Pattern - State Pattern Applied - Best Practise

Hi there!

Today i'm gonna show the state design pattern in action. The state design pattern is a very useful programming design pattern while dealing with state maschines.

I'm assuming here you already know the concepts and i'll be focusing on practise. The example i will provide is a nice way to show it how it could looks like. You can always come back here, take it, adapt it and use it in your applictions as you may need. So be sure you bookmark it or join the group here on the right side of this post subscribing it.

First of all, let's take a look at the UML diagram of it. After that we will take the analogy for our example.

The simpliest UML State Pattern

Pay close attention, because once you understand that, everything will become clear and simple to understand. That's the reason I'm putting always the UML first. That way you'll get an eye for it with the time.

The State Interface

Here we can see how simple it is. Let's implement some real states from our ticket automat to get more familiar with. The MoneyState will be set, when the user has paid for the ticket and will calculate the exchange if some.

public interface State {
    void handle();
}
public class MoneyState implements State {
    private TicketAutomat context;
    public MoneyState(TicketAutomat context) {
        this.context = context;
    }
    @Override
    public void handle() {
        System.out.println("PAYMENT RECEIVED! CALCULATING EXCHANGE ...");
        context.setCurrentState(context.getSoldState());
        context.getCurrentState().handle();
    }
}

The TicketSoldState

This state says to the user that the ticket has been sold out successfully and sets the NoMoneyState asking the user to pay for a ticket if we still have tickets available, otherwise will set the SoldOutStore which shows to the user, that all tickets are sold out.

public class TicketSoldState implements State {
    private TicketAutomat context;
    public TicketSoldState(TicketAutomat context) {
        this.context = context;
    }
    @Override
    public void handle() {
        context.updateTicketsAvailable();
        context.setPaid(false);
        System.out.println("YOUR TICKET WAS PRINTED OUT!");
        if(context.isTicketAvailable()){
            context.setCurrentState(context.getNoMoneyState());
        }else{
            context.setCurrentState(context.getSoldOutState());
            context.getCurrentState().handle();
        }
    }
}

TicketsSoldOutState

This state says, "Sorry no more tickets available" or sets the NoMoneyState if we have tickets available.

public class TicketsSoldOutState implements State {
    private TicketAutomat context;
    public TicketsSoldOutState(TicketAutomat context) {
        this.context = context;
    }
    @Override
    public void handle() {
        if(context.isTicketAvailable()){
            context.getNoMoneyState().handle();
        }else{
            System.out.println("SORRY! THERE IS NO MORE TICKETS AVAILABLE!");
            context.setCurrentState(context.getSoldOutState()); 
        }
    }
}

NoMoneyState

This state is the first message the user will see, if we have tickets to sell. It sets the MoneyState to show to the user, that it has received the money and it is processing it.

public class NoMoneyState implements State {
    private TicketAutomat context;
    public NoMoneyState(TicketAutomat context) {
        this.context = context;
    }
    @Override
    public void handle() {
        System.out.println("PLEASE PAY FOR THE TICKET YOU WANT!");
        context.setCurrentState(context.getMoneyState());
        context.getCurrentState().handle();
    }
}

The Context - TicketAutomat

This is the context class. It contais all states and has the some simple logic in it

// THE TICKET AUTOMAT IS THE CONTEXT
public class TicketAutomat {
    
    private State soldOutState;
    private State noMoneyState;
    private State moneyState;
    private State soldState;
    private State currentState;
    
    private int ticketsAvailable = 10;
    private boolean paid=false;
    
    TicketAutomat(int ticketsAvailable){
        soldState =  new TicketSoldState(this);
        soldOutState =  new TicketsSoldOutState(this);
        noMoneyState =  new NoMoneyState(this);
        moneyState =  new MoneyState(this);
        initCurrentState(ticketsAvailable);
    }
    
    private void initCurrentState(int ticketsAvailable){
        this.ticketsAvailable=ticketsAvailable;
        if(this.ticketsAvailable>0){
            setCurrentState(noMoneyState);
        }else{
            setCurrentState(soldOutState);
        }
    }
        
    public State getSoldOutState() {return soldOutState;}
    public State getNoMoneyState() {return noMoneyState;}
    public State getMoneyState() {return moneyState;}
    public State getSoldState() {return soldState;}
    public State getCurrentState() {return currentState;}
    public void setCurrentState(State currentState) {this.currentState = currentState;}
    public int getTicketsAvailable() {return ticketsAvailable;}
    public void setTicketsAvailable(int ticketsAvailable) {this.ticketsAvailable = ticketsAvailable;}
    public void updateTicketsAvailable(){this.ticketsAvailable-=1;}
    public boolean isTicketAvailable(){return this.ticketsAvailable>0;}
    public boolean hasPaid(){return paid;}
    public void setPaid(boolean paid){this.paid=paid;}
}

Testing it

Finally our client which tests it. With a Thread we are simulating a full cycle waiting 5 seconds between each state.

public class Client {
    public static void main(String[] args) {
        new Thread(){
            int ticketsAvailable = 5;
            final TicketAutomat automat = new TicketAutomat(ticketsAvailable);
            public void run() {
                while(automat.isTicketAvailable()){
                    automat.setPaid(true);
                    automat.getCurrentState().handle();
                    try {
                        sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();
    }
}
That's all! Hope you like it!

1 comment:

  1. Hello!

    I have lately checked the design pattern State. I think I see a little change in the conception of the design pattern I saw in
    http://en.wikipedia.org/wiki/State_pattern
    where is also a Java implementation for that. In Your implementation the TicketAutomat, and each State have less responsibility. The Client class have to know about the inner depth of the TicketAutomat class: automat.getCurrentState().handle().
    In Your implementation We cut down the initialization cost of each possible state to 1 initalization / class, but we loose the set up when we change state. The wiki page also emphasizes on that in the example. The private int count = 0; runs every state change.
    In Your implementation, due to the already initilaized states, every State is stored in the memory in TicketAutomat. Also when we add more States, TicketAutomat have to be extended. In the wiki example You can add new State without modifying the StateContext.

    Br,
    Márton

    ReplyDelete