State Design Pattern

Vending Machine Design

Introduction

This pattern is primarily used for vending machines and in specific areas where a particular state has a defined set of allowed operations. In these areas, the state design pattern is also employed. Now, let's jump directly into the design part of the vending machine.

Vending machines automatically dispense the products without any sales supervisor. These machines can have a wide range of features and capabilities. This design will have all the core features of vending machines.

Happy path: The user presses the cash button and inserts the cash in the insert tray. Then user presses the product button and enters the product code on the dialpad. The product gets dispensed at the collect product. If the cash input is higher than the price of the product remaining change will be disbursed at the cash change tray. If the user presses the cancel button in the middle of the process it will refund the user's cash at the cash change tray.

State Diagram of Vending Machine

If the machine doesn't perform any action by itself then we consider it as a state. If the user interacts with the machine then this is called operation. Each operation triggers a state change.

In the above diagram, the Dispense product is in a momentary state and can be included in the selection state itself.

StateOperation
IdlePress the Insert Cash Button
Has MoneyInsert Coin, Select the product button, Cancel/Refund button
SelectionChoose Product, Cancel / Refund, Return change
DispensingProduct Dispense

If there is a problem that has a set of allowed operations for a particular state then our go-to pattern is the State design pattern.

State Design Pattern

Create a State interface and write down all the operations. Now create different states that implement this interface. Each class implements operations that the state can perform and all other methods will throw exceptions.

UML Diagram of vending machine

VendingMachine Object knows 3 things these are current state of the machine, the current stock status, and the current money it possesses. The main aspect is that the state interface remains constant, while various implementations can be applied to elements such as inventory, item shelf, and item objects. Even the modification of the inventory logic can vary

Sample Code

State interface

package VendingStates;

import machine.Coin;
import machine.Item;
import machine.VendingMachine;

import java.util.List;

public interface State {

    public void clickOnInsertCoinButton(VendingMachine machine) throws Exception;

    public void clickOnStartProductSelectionButton(VendingMachine machine) throws Exception;

    public void insertCoin(VendingMachine machine, Coin coin) throws Exception;

    public void chooseProduct(VendingMachine machine, int codeNumber) throws Exception;

    public void getChange(int returnChangeMoney) throws Exception;

    public Item dispenseProduct(VendingMachine machine, int codeNumber) throws Exception;

    public List<Coin> refundFullMoney(VendingMachine machine) throws Exception;

    public void updateInventory(VendingMachine machine, Item item, int codeNumber) throws Exception;
}

Has Money state

package VendingStates.Impl;

import VendingStates.State;
import machine.Coin;
import machine.Item;
import machine.VendingMachine;

import java.util.List;

public class HasMoneyState implements State {

    public HasMoneyState() {
        System.out.println("Currently Vending machine is in HasMoneyState");
    }

    @Override
    public void clickOnInsertCoinButton(VendingMachine machine) throws Exception {
        return;
    }

    @Override
    public void clickOnStartProductSelectionButton(VendingMachine machine) throws Exception {
        machine.setVendingMachineState(new SelectionState());
    }

    @Override
    public void insertCoin(VendingMachine machine, Coin coin) throws Exception {
        System.out.println("Accepted the coin");
        machine.getCoinList().add(coin);
    }

    @Override
    public void chooseProduct(VendingMachine machine, int codeNumber) throws Exception {
        throw new Exception("you need to click on start product selection button first");
    }

    @Override
    public void getChange(int returnChangeMoney) throws Exception {
        throw new Exception("you can not get change in hasMoney state");
    }

    @Override
    public Item dispenseProduct(VendingMachine machine, int codeNumber) throws Exception {
        throw new Exception("product cannot be dispensed in hasMoney State");
    }

    @Override
    public List<Coin> refundFullMoney(VendingMachine machine) throws Exception {
        System.out.println("Returned the full amount back in the Coin Dispense Tray");
        machine.setVendingMachineState(new IdleState(machine));
        return machine.getCoinList();
    }

    @Override
    public void updateInventory(VendingMachine machine, Item item, int codeNumber) throws Exception {
        throw new Exception("you cannot update inventory in hasMoney state");
    }
}

Code Repo - link