Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement state design pattern without violating Interface Segregation Principle (ISP)?

I'm trying to implement vending machine using state design pattern. The problem is that the state interface contain some methods which might not be relevant to some concrete implementations of the state interface. As a result it violates the interface segregation principle. So how can we implement it without violating it? How will the project structure and implementation of state context will look like?

My state interface :

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 int 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; }

Concrete state implementations:

public class IdleState implements State {

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

    public IdleState(VendingMachine machine){
        System.out.println("Currently Vending machine is in IdleState");
        machine.setCoinList(new ArrayList<>());
    }

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

    @Override
    public void clickOnStartProductSelectionButton(VendingMachine machine) throws Exception {
        throw new Exception("first you need to click on insert coin button");

    }

    @Override
    public void insertCoin(VendingMachine machine, Coin coin) throws Exception{
        throw new Exception("you can not insert Coin in idle state");
    }

    @Override
    public void chooseProduct(VendingMachine machine, int codeNumber) throws Exception{
        throw new Exception("you can not choose Product in idle state");
    }

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

    @Override
    public List<Coin> refundFullMoney(VendingMachine machine) throws Exception{
        throw new Exception("you can not get refunded in idle state");
    }

    @Override
    public Item dispenseProduct(VendingMachine machine, int codeNumber) throws Exception{
        throw new Exception("proeduct can not be dispensed idle state");
    }

    @Override
    public void updateInventory(VendingMachine machine, Item item, int codeNumber) throws Exception {
        machine.getInventory().addItem(item, codeNumber);
    }
}

3 more concrete state implementations are there with some not all methods overridden.

State Context:

public class VendingMachine {

    private State vendingMachineState;
    private Inventory inventory;
    private List<Coin> coinList;

    public VendingMachine(){
        vendingMachineState = new IdleState();
        inventory = new Inventory(10);
        coinList = new ArrayList<>();
    }

    public State getVendingMachineState() {
        return vendingMachineState;
    }

    public void setVendingMachineState(State vendingMachineState) {
        this.vendingMachineState = vendingMachineState;
    }

    public Inventory getInventory() {
        return inventory;
    }

    public void setInventory(Inventory inventory) {
        this.inventory = inventory;
    }

    public List<Coin> getCoinList() {
        return coinList;
    }

    public void setCoinList(List<Coin> coinList) {
        this.coinList = coinList;
    }
}
like image 603
Samarth Mehrotra Avatar asked Oct 19 '25 20:10

Samarth Mehrotra


1 Answers

The interface segregation principle is about the interface supplied to clients of a service. The idea is that if the caller of an interface only uses a subset of methods on the interface, it suggests that the interface has too many responsibilities: the ones used by this caller, and the ones that are only used by other callers can be separated.

On the implementation side, implementations of all interface methods need to be provided. There's no way for the caller to know which methods are valid for the current state without violating encapsulation.

In this specific example, you can think of the interface methods as the physical controls on a mechanical vending machine, and the caller as the person standing in front of it. In a real vending machine, the physical controls can't rearrange themselves whenever the state changes. There's no way to prevent a person from pressing the "wrong" button for the current state, so there needs to be a defined behaviour for when that does happen. In practice, the machine may choose to show an error on a display, flash an LED, play a sound, or do nothing at all. These are all valid implementations of these methods!

An alternative design might be to have each state implement a different interface, and have each method on a state return the new state. This option can be good when there really is a different interface to present to the caller. To stretch this example, maybe the vending machine uses a touch screen display instead of mechanical controls.

One drawback of this alternative is that there is no way to guarantee that a caller won't retain a reference to an outdated state and call methods on it. You can turn this into a runtime error by ensuring that each state instance changes to throw exceptions from every method after it has been "used". In some cases, you may be able to allow calls on the old state to continue working, as if time-travelling back to the earlier state. Whether this makes sense for a particular use case is application dependent — you wouldn't want it for a vending machine, for example.

like image 110
Tim Moore Avatar answered Oct 21 '25 09:10

Tim Moore