JIYIK CN >

Current Location:Home > Learning > ALGORITHM >

How to use the State Design Pattern in Java?

Author:JIYIK Last Updated:2025/03/19 Views:

The State design pattern is a behavioral pattern. The State pattern looks similar to the Strategy pattern, but it helps in managing the object states so that they behave differently in different states. In this example, we will take a famous Object-Oriented Design interview question and implement a Vending Machine in Java. In the past, we have solved this problem without using any design pattern, but here we will use the State design pattern to create a vending machine with different states. This example will not only help us understand how to implement the State design pattern in Java, but also give us an experience of when to use the State pattern in our applications .

If you understand the working principle of the vending machine, you can divide its operation into four main states: sold out, idle, in process, and sold. The vending machine is sold out when it is just started and not initialized, or when all the goods have been sold out.

Idle , when the vending machine is waiting for the customer to select the product; Processing , once the customer has selected the product and started inserting coins; Sold , when the customer has paid the amount.

We can use the State design pattern to model these states of a vending machine. In this example, we have an Stateabstract class called to represent the states of a Vending Machine, which provides default implementations of various methods that are called by the Context (Vending Machine in this case), and each of our States, such as Idle, Processing, Sold, SoldOut then extend this abstract class and override methods that can be called in those states.

All other methods which should not be called on these states will perform default action which can be do nothing or throw exception as defined in State abstract class. Each State class keeps a reference to Context through which they are associated and they also do state transitions, I mean changing the current state of Vending Machine.


How to Design a Vending Machine in Java Using State Design Pattern - Example

Here is our complete code example for implementing a vending machine using the state design pattern in Java. When the Vending Machine starts, it is initially in the SoldOut state and then goes to the Idle state after being initialized with a default number of Items and a default number of coins.

State Design Pattern Vending Machine

select(Item i)Vending Machine also provides a method like , insert(Coin c), etc. which are present in the State abstract class refund()but they are delegated to the current state. When a customer select(Item i)selects a product by calling on the Vending Machine, it delegates to which is valid if the Machine is in Idle state then it will go into Processing but will throw an IllegalStateExceptioncurrentState.select(i) if called on any other machine state.

Now, let’s look at the code, it is very similar to our vending machine problem code, but this time, I have used the state pattern to solve the problem.

  • Coin.java
  • Idle.java
  • inventory.java
  • Item.java
  • NotSufficientChangeException.java
  • Processing.java
  • Sold.java
  • SoldOut.java
  • State.java
  • VendingMachine.java
  • VendingMachineTest.java

Coin.java

public enum Coin {
    PENNY(1), NICKLE(5), DIME(10), QUARTER(25);

    private int value;
    private Coin(int value){
        this.value = value;
    }

    public int value(){
        return value;
    }
}

State.java

import java.util.List;

public class State {

    public void insert(Coin c){
        throw new IllegalStateException();
    }

    public List refund(){
         throw new IllegalStateException();
    }

    public int choose(Item i){
         throw new IllegalStateException();
    }

    public Item dispense(){
         throw new IllegalStateException();
    }

    public List getChange() {
        throw new IllegalStateException();
    }
}

Idle.java

public class Idle extends State{

    private VendingMachine machine;

    public Idle(VendingMachine machine){
        this.machine = machine;
    }

    @Override
    public int choose(Item i) {
        if(machine.itemInvertory.getCount(i) >= 1){
            machine.currentItem = i;
            machine.setState(new Processing(machine));
        }else{
            System.out.println(i + " sold out, Please try another drink");
        }
        return i.getPrice();
    }

}

Sold.java

import java.util.List;

public class Sold extends State{
    private VendingMachine machine;

    public Sold(VendingMachine machine){
        this.machine = machine;
    }

    @Override
    public Item dispense(){
        if(machine.itemInvertory.isEmpty()){
            machine.setState(new SoldOut(machine));
        }
        machine.balance = machine.balance  - machine.currentItem.getPrice();
 
        machine.itemInvertory.take(machine.currentItem);
 
        Item sold = machine.currentItem;
        machine.currentItem = null;
 
        return sold;
    }

    @Override
    public List getChange(){
        List change = machine.getChange(machine.balance);
        return change;
    }
}

SoldOut.java

public class SoldOut extends State{

    private VendingMachine machine;

    public SoldOut(VendingMachine machine){
        this.machine = machine;
    }

}

Inventory.java

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public class Inventory {
    private Map store = new ConcurrentHashMap();

    public int getCount(I item){
        Integer count = store.get(item);
        return count != null ? count : 0;
    }

    public void add(I item){
        int count = getCount(item);
        store.put(item, ++count);
    }

    public void take(I item){
        int count = getCount(item);
        store.put(item, --count);
    }

    public boolean isEmpty(){
        return store.isEmpty();
    }

    boolean has(I i) {
        return getCount(i) > 0;
    }
}

Item.java

public enum Item {
    COKE(70), PEPSI(80), SPRITE(90);

    private int price;

    private Item(int price){
        this.price = price;
    }

    public int getPrice(){
        return price;
    }
}

NotSufficientChangeException.java

public class NotSufficientChangeException extends RuntimeException {

    private String message;

    public NotSufficientChangeException(String string) {
        this.message = string;
    }

    @Override
    public String getMessage() {
        return message;
    }

}

Processing.java

import java.util.List;

public class Processing extends State{

    private VendingMachine machine;

    public Processing(VendingMachine machine){
        this.machine = machine;
    }

    @Override
    public void insert(Coin c) {
        machine.coinInvertory.add(c);
        machine.balance = machine.balance + c.value();
        if (machine.balance >= machine.currentItem.getPrice()) {
     
            if (machine.hasChange(machine.balance - machine.currentItem.getPrice())) {
                machine.setState(new Sold(machine));
            } else {
                System.out.println("Machine don't have 
                  sufficient change, Please take refund");
            }
        }

    }


    @Override
    public List refund() {
        machine.currentItem = null;
        machine.setState(new Idle(machine));
        List change = machine.getChange(machine.balance);
        machine.balance = 0;
        return change;
    }

}

VendingMachine.java

import java.util.ArrayList;
import java.util.List;


public class VendingMachine {
    private State state;
    Inventory itemInvertory = new Inventory();
    Inventory coinInvertory = new Inventory();

    Item currentItem;
    int balance;

    public VendingMachine(){
        state = new SoldOut(this);
        initialize();
    }

    public void insert(Coin c){
        state.insert(c);
    }
    public List refund(){
        return state.refund();
    }
    public int choose(Item i){
       return state.choose(i);
    }

    public Item dispense(){
        return state.dispense();
    }

    public void setState(State newState){
        state = newState;
    }

    public List getChange(){
        return state.getChange();
    }

    private void initialize() {
       loadCoins();
       loadItems();
       this.state = new Idle(this);
    }

    private void loadCoins(){
        for(Coin c: Coin.values()){
           coinInvertory.add(c);
           coinInvertory.add(c);
           coinInvertory.add(c);
           coinInvertory.add(c);
           coinInvertory.add(c);
       }
    }

    private void loadItems(){
        for(Item i: Item.values()){
           itemInvertory.add(i);
           itemInvertory.add(i);
           itemInvertory.add(i);
           itemInvertory.add(i);
           itemInvertory.add(i);
       }
    }

    List getChange(int balance) {
        List change = new ArrayList();
        while(balance != 0){
            if(balance >= Coin.QUARTER.value() && coinInvertory.has(Coin.QUARTER)){
                balance -= Coin.QUARTER.value();
                change.add(Coin.QUARTER); coinInvertory.take(Coin.QUARTER);
         
            }else if(balance >= Coin.DIME.value() && coinInvertory.has(Coin.DIME) ) {
                balance -= Coin.DIME.value();
                change.add(Coin.DIME); coinInvertory.take(Coin.DIME);
         
            }else if(balance >= Coin.NICKLE.value() && coinInvertory.has(Coin.NICKLE)){
                balance -= Coin.NICKLE.value();
                change.add(Coin.NICKLE); coinInvertory.take(Coin.NICKLE);
         
            }else if(balance >= Coin.PENNY.value() && coinInvertory.has(Coin.PENNY)) {
                balance -= Coin.PENNY.value();
                change.add(Coin.PENNY); coinInvertory.take(Coin.PENNY);
            }
     
           if(coinInvertory.isEmpty() && balance >0){
               throw new NotSufficientChangeException("Not Sufficient 
                        Change for this purchase");
           }
        }
 
        return change;
    }

    boolean hasChange(int change) {
        try{
           List coins = getChange(change);
     
           //returning coins back to inventory
           for(Coin c : coins){
               coinInvertory.add(c);
           }
     
        }catch(NotSufficientChangeException ex){
            return false;
        }  
        return true;
    }

}

VendingMachineTest.java

Here is our test program to test the VendingMachine code that we implemented using the state design pattern. These tests will confirm that the state transitions work as expected. The code below uses JUnit annotations, so we need the JUnit 4.0 or JUnit 5.0 framework JAR file in our classpath. I ran a few tests to buy drinks with change and more change to see if our machine is working properly.

For your practice, I have also added some blank test methods, such as buyMultipleDrinks(), refund()and buyAllDrinks(), which we can implement to practice writing JUnit test cases.

This UML diagram of the State design pattern will also help us understand the class structure and the relationship between them.

State Design Pattern UML Diagram

import java.util.List;

import org.junit.Test;

import static org.junit.Assert.*;

public class VendingMachineTest {
    private VendingMachine machine = new VendingMachine();

    public VendingMachineTest() {
        System.out.println("JUnit Framework calls Constructor of test class before executing test methods");
    }

    @Test
    public void buyDrinkWithExactAmount() {
        int price = machine.choose(Item.COKE);
        assertEquals(70, price);
        assertEquals(Item.COKE, machine.currentItem);
        machine.insert(Coin.QUARTER);
        machine.insert(Coin.QUARTER);
        machine.insert(Coin.DIME);
        machine.insert(Coin.DIME);
        assertEquals(70, machine.balance);
        assertEquals(7, (int) machine.coinInvertory.getCount(Coin.DIME));
        assertEquals(7, (int) machine.coinInvertory.getCount(Coin.QUARTER));
        Item i = machine.dispense();
        assertEquals(Item.COKE, i);
        assertEquals(4, (int) machine.itemInvertory.getCount(i));
        List change = machine.getChange();
        assertTrue(change.isEmpty());
    }

    @Test
    public void buyDrinkWithMoreAmount() {
        int price = machine.choose(Item.SPRITE);
        assertEquals(90, price);
        assertEquals(Item.SPRITE, machine.currentItem);
        machine.insert(Coin.QUARTER);
        machine.insert(Coin.QUARTER);
        machine.insert(Coin.QUARTER);
        machine.insert(Coin.QUARTER);
        assertEquals(100, machine.balance);
        assertEquals(9, (int) machine.coinInvertory.getCount(Coin.QUARTER));
        Item i = machine.dispense();
        assertEquals(Item.SPRITE, i);
        assertEquals(4, machine.itemInvertory.getCount(i));
        assertEquals(5, machine.itemInvertory.getCount(Item.COKE));
        List change = machine.getChange();
        assertEquals(1, change.size());
        assertEquals(Coin.DIME, change.get(0));
        assertEquals(4, machine.coinInvertory.getCount(Coin.DIME));
    }

    @Test
    public void buyMultipleDrinks() {
        //TODO
    }

    @Test
    public void refund() {
        //TODO
    }

    @Test
    public void buyAllDrinks() {
        //TODO
    }
}

That’s all about how to implement a vending machine in Java using the state design pattern . Compared to our previous examples, we can see a clear advantage in managing the logic of changing states and implementing different behaviors in different states. I strongly recommend everyone to use the state design pattern in Java to implement a vending machine or any state-oriented problem. It leads to a simpler and efficient solution.

For reprinting, please send an email to 1244347461@qq.com for approval. After obtaining the author's consent, kindly include the source as a link.

Article URL:

Related Articles

Java Decorator Design Pattern Example

Publish Date:2025/03/19 Views:115 Category:ALGORITHM

Hello everyone, if you want to learn about Decorator Design Pattern in Java , then you have come to the right place. As design patterns are very important while building software and equally important in any core Java interview, it is alway

Adapter vs Decorator vs Facade vs Proxy Design Pattern in Java

Publish Date:2025/03/19 Views:67 Category:ALGORITHM

There are some striking similarities between the Adapter, Decorator, Facade, and Proxy design patterns as they all use composition and delegation to solve problems. The Adapter pattern wraps an interface and delegates calls to it. Decorator

Observer Design Pattern in Java

Publish Date:2025/03/19 Views:70 Category:ALGORITHM

Observer design pattern in Java is a basic core Java pattern where the Observer monitors any changes in the state or properties of the Subject . For example, a company updates all shareholders about any decision taken by them here the compa

How to use Composite Design Pattern in Java?

Publish Date:2025/03/19 Views:179 Category:ALGORITHM

Hello Java programmers, if you want to learn about Composite Design Pattern in Java , such as how to implement Composite Design Pattern and when to use it, then you have come to the right place. In this article, we will discuss Composite De

How to find the IP address of the local host in Java

Publish Date:2025/03/17 Views:98 Category:NETWORK

The Java Networking API provides a way to find the local host IP address from a Java program using the java.net InetAddress class. It is rare that you need the local host IP address in a Java program. Most of the time, I use the Unix command to find t

Why do you need to bind event handlers in React Class Components?

Publish Date:2025/03/16 Views:58 Category:React

When using React, we must have come across control components and event handlers. We need to use `.bind()` in the constructor of the custom component to bind these methods to the component instance. As shown in the following code:

Do you understand JavaScript closures?

Publish Date:2025/02/21 Views:111 Category:JavaScript

The function of a closure can be inferred from its name, suggesting that it is related to the concept of scope. A closure itself is a core concept in JavaScript, and being a core concept, it is naturally also a difficult one.

Do you know about the hidden traps in variables in JavaScript?

Publish Date:2025/02/21 Views:183 Category:JavaScript

Whether you're just starting to learn JavaScript or have been using it for a long time, I believe you'll encounter some traps related to JavaScript variable scope. The goal is to identify these traps before you fall into them, in order to av

How much do you know about the Prototype Chain?

Publish Date:2025/02/21 Views:156 Category:JavaScript

The prototype chain can be considered one of the core features of JavaScript, and certainly one of its more challenging aspects. If you've learned other object-oriented programming languages, you may find it somewhat confusing when you start

Scan to Read All Tech Tutorials

Social Media
  • https://www.github.com/onmpw
  • qq:1244347461

Recommended

Tags

Scan the Code
Easier Access Tutorial