How to use the State Design Pattern in Java?
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 State
abstract 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.
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.
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.
Related Articles
Store Div Id in PHP variable and pass it to JavaScript
Publish Date:2025/04/13 Views:51 Category:PHP
-
This article shows you how to div id store a in a PHP variable and pass it to JavaScript code. We will answer the following questions. What is div id ? How to div id store in a PHP variable? How to pass variables to JavaScript code? Let’s
Use of Linux command at - set time to execute command only once
Publish Date:2025/04/08 Views:158 Category:OPERATING SYSTEM
-
This article mainly involves a knowledge point, which is the atd service. Similar to this service is the crond service. The functions of these two services can be similar to the two functional functions of javascript. Those who have learned
Design Patterns in Java - Visitor Pattern
Publish Date:2025/03/19 Views:175 Category:ALGORITHM
-
Today, we are going to learn one of the most useful patterns, the Visitor Pattern. What is the Visitor pattern? Well, let's look at an example. Let's say you're a software engineer working at a university. The university rarely has establis
How to use Strategy Pattern in Java?
Publish Date:2025/03/19 Views:142 Category:ALGORITHM
-
Hi everyone, you might have heard, “Can you tell me about any design pattern other than Singleton design pattern that you have used recently in your project?”. This is one of the popular questions in various Java interviews in recent ye
Difference between Proxy Mode and State Mode in Java
Publish Date:2025/03/19 Views:76 Category:ALGORITHM
-
Hi guys, if you are preparing for Java interview and looking for difference between Proxy and State design pattern, then you are at the right place. In the past, I have explained several important object-oriented design patterns like State,
Strategy Design Pattern and Open-Closed Principle in Java
Publish Date:2025/03/19 Views:114 Category:ALGORITHM
-
The Strategy design pattern is based on the Open/Closed design principle , the famous " O SOLID " of design principles . It is one of the patterns that has become popular in the field of object-oriented analysis and design, along with the D
How to implement binary search in Java without recursion?
Publish Date:2025/03/19 Views:198 Category:ALGORITHM
-
Hey Java programmers, if you want to implement binary search in Java and looking for iterative and recursive binary search algorithms, then you have come to the right place. Today I am going to teach you an important algorithm. In computer
How to implement the singleton pattern in JavaScript ES6+
Publish Date:2025/03/19 Views:55 Category:ALGORITHM
-
In this article, we will show you how to implement the singleton pattern in JavaScript. If we are a full-stack JavaScript developer, we know that JavaScript is a powerful language and we can build amazing websites with it. On the other hand
How to use the Adapter design pattern in Java
Publish Date:2025/03/19 Views:77 Category:ALGORITHM
-
The Adapter design pattern in Java , also known as the Wrapper pattern, is another very useful GOF pattern that helps bridge the gap between two classes in Java. According to the Gang of Four pattern list, Adapter is a structural pattern, m