DEV Community

NOOB
NOOB

Posted on

LLD:14-ATM Machine System

ATM Machine System - State Pattern Implementation

This is an implementation of the State design pattern for an ATM Machine. By utilizing this pattern, the ATM can alter its behavior based on its internal state (Idle, Card Inserted, PIN Entered, etc.) without relying on complex, nested if/else logic.

Problem Statement

Building the software for an ATM that transitions between distinct operational states: Idle, Card Inserted, PIN Entered, Withdrawing Cash, and Out of Cash. The system must restrict invalid operations based on the current state.

Key Challenge:

  • If a user tries to withdrawCash() in the Idle state, it should throw an error or prompt the user to insert their card first.
  • The happy path must flow sequentially: Idle -> insertCard() -> Card Inserted -> enterPin() -> PIN Entered -> withdrawCash() -> Cash Dispensed (which automatically returns the machine to Idle).

Class Diagram

(Note: The diagram has been updated from your prompt to reflect the ATM domain rather than an Order Processing system.)

                            +----------------------+
                            |      ATMMachine      | <-------------------+
                            +----------------------+                     |
                            | - currentState       |                     |
                            | - totalCash          |                     |
                            +----------------------+                     |
                            | + setState()         |                     |
                            | + insertCard()       |                     |
                            | + enterPin()         |                     |
                            | + withdrawCash()     |                     |
                            +----------+-----------+                     |
                                       |                                 |
                                       | (Delegates to)                  |
                                       v                                 |
                            +----------------------+                     |
                            |        State         |                     |
                            |      (Interface)     |                     |
                            +----------------------+                     |
                            | + insertCard()       |                     |
                            | + enterPin()         |                     |
                            | + withdrawCash()     |                     |
                            +----------+-----------+                     |
                                       ^                                 |
                                       | (Implements)                    |
            +--------------------------+--------------------------+      |
            |                          |                          |      |
 +----------+----------+    +----------+----------+    +----------+------+-+
 |      IdleState      |    |   InsertCardState   |    |   EnterPinState   |
 +---------------------+    +---------------------+    +-------------------+
 | - atmMachine        |--->| - atmMachine        |--->| - atmMachine      |
 | + insertCard()      |    | + insertCard()      |    | + insertCard()    |
 | + enterPin()        |    | + enterPin()        |    | + enterPin()      |
 | + withdrawCash()    |    | + withdrawCash()    |    | + withdrawCash()  |
 +---------------------+    +---------------------+    +-------------------+
Enter fullscreen mode Exit fullscreen mode

Implementation

(Note: I updated IdealState to IdleState to align with the standard naming convention for resting states in finite-state machines).

package state.atmmachine;

public class ATMMachineSystem {

    /**
     * State Interface.
     * Defines the common actions a user can attempt at the ATM.
     */
    interface State {
        void insertCard();
        void enterPin();
        void withdrawCash(int cash);
    }

    /**
     * Context Class.
     * Maintains a reference to the current state and delegates user actions to it.
     */
    static class ATMMachine {
        State idleState;
        State insertCardState;
        State enterPinState;
        State withdrawCashState;
        State outOfCashState;

        State currentState;
        int totalCash;

        public ATMMachine(int totalCash) {
            idleState = new IdleState(this);
            insertCardState = new InsertCardState(this);
            enterPinState = new EnterPinState(this);
            withdrawCashState = new WithdrawCashState(this);
            outOfCashState = new OutOfCashState(this);

            this.totalCash = totalCash;
            if (totalCash > 0) {
                currentState = idleState;
            } else {
                currentState = outOfCashState;
            }
        }

        public void setState(State state) {
            this.currentState = state;
        }

        public void insertCard() {
            currentState.insertCard();
        }

        public void enterPin() {
            currentState.enterPin();
        }

        public void withdrawCash(int cash) {
            currentState.withdrawCash(cash);
        }
    }

    /**
     * Concrete State: Waiting for a user.
     */
    static class IdleState implements State {
        private final ATMMachine atmMachine;

        public IdleState(ATMMachine atmMachine) {
            this.atmMachine = atmMachine;
        }

        @Override
        public void insertCard() {
            System.out.println("Insert Card to withdraw money.");
            atmMachine.setState(atmMachine.insertCardState);
        }

        @Override
        public void enterPin() {
            System.out.println("ERROR: Insert Card first!");
        }

        @Override
        public void withdrawCash(int cash) {
            System.out.println("ERROR: Enter pin first!");
        }
    }

    /**
     * Concrete State: Card is in, waiting for authentication.
     */
    static class InsertCardState implements State {
        private final ATMMachine atmMachine;

        public InsertCardState(ATMMachine atmMachine) {
            this.atmMachine = atmMachine;
        }

        @Override
        public void insertCard() {
            System.out.println("ERROR: Card is already inserted!");
        }

        @Override
        public void enterPin() {
            System.out.println("Enter pin to withdraw cash!");
            atmMachine.setState(atmMachine.enterPinState);
        }

        @Override
        public void withdrawCash(int cash) {
            System.out.println("ERROR: Enter pin to withdraw cash!");
        }
    }

    /**
     * Concrete State: PIN verified, waiting for amount.
     */
    static class EnterPinState implements State {
        private final ATMMachine atmMachine;

        public EnterPinState(ATMMachine atmMachine) {
            this.atmMachine = atmMachine;
        }

        @Override
        public void insertCard() {
            System.out.println("ERROR: Card is already inserted!");
        }

        @Override
        public void enterPin() {
            System.out.println("ERROR: Pin is already inserted");
        }

        @Override
        public void withdrawCash(int cash) {
            System.out.println("Cash is here, please collect!");
            atmMachine.setState(atmMachine.withdrawCashState);
            atmMachine.withdrawCash(cash); // Auto-transitions and dispenses
        }
    }

    /**
     * Concrete State: Dispensing logic and balance checking.
     */
    static class WithdrawCashState implements State {
        private final ATMMachine atmMachine;

        public WithdrawCashState(ATMMachine atmMachine) {
            this.atmMachine = atmMachine;
        }

        @Override
        public void insertCard() {
            System.out.println("ERROR: Card is already inserted!");
        }

        @Override
        public void enterPin() {
            System.out.println("ERROR: Pin is already inserted");
        }

        @Override
        public void withdrawCash(int cash) {
            if (atmMachine.totalCash >= cash) {
                atmMachine.totalCash -= cash;
                System.out.println("Success! Dispensing $" + cash + ". Please collect your cash.");
                System.out.println("Remaining ATM balance: $" + atmMachine.totalCash);

                if (atmMachine.totalCash > 0) {
                    atmMachine.setState(atmMachine.idleState);
                } else {
                    System.out.println("ATM Machine is out of cash!");
                    atmMachine.setState(atmMachine.outOfCashState);
                }
            } else {
                System.out.println("ERROR: Insufficient funds in ATM.");
                atmMachine.setState(atmMachine.idleState);
            }
        }
    }

    /**
     * Concrete State: Machine is empty, denying all interactions.
     */
    static class OutOfCashState implements State {
        private final ATMMachine atmMachine;

        public OutOfCashState(ATMMachine atmMachine) {
            this.atmMachine = atmMachine;
        }

        @Override
        public void insertCard() {
            System.out.println("ERROR, Machine is out of cash!");
        }

        @Override
        public void enterPin() {
            System.out.println("ERROR, Machine is out of cash!");
        }

        @Override
        public void withdrawCash(int cash) {
            System.out.println("ERROR, Machine is out of cash!");
        }
    }

    /**
     * Main driver method.
     */
    public static void main(String[] args) {
        System.out.println("---- ATM Machine Booting UP ----");

        ATMMachine atmMachine = new ATMMachine(50);

        System.out.println("\n---- Attempt 1: Direct withdrawing cash ----");
        atmMachine.withdrawCash(30);

        System.out.println("\n---- Attempt 2: Happy Path ----");
        atmMachine.insertCard();
        atmMachine.enterPin();
        atmMachine.withdrawCash(30);
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Features

  • State Interface Integration: Enforces a rigid contract (insertCard, enterPin, withdrawCash) that all machine states must implement, preventing unhandled states.
  • Internal Transitions: States control their own routing to the next logical state by calling atmMachine.setState().
  • Validation-First: The machine organically defends against sequence breaking (e.g., trying to withdraw cash without providing a PIN) by returning context-aware error messages depending on the current active state.
  • Auto-Delegation: The ATMMachine class remains incredibly clean; its methods are merely wrappers that forward instructions to whatever state is currently active.

How It Works

  1. Booting Up: ATMMachine starts with $50. Because it has money, it defaults to the IdleState.
  2. Attempt 1 (Direct Withdrawal): The client attempts withdrawCash(30). The request is routed to IdleState.withdrawCash(), which safely rejects it since no card is present.
  3. Attempt 2 (Happy Path):
    • insertCard() transitions the machine to InsertCardState.
    • enterPin() transitions the machine to EnterPinState.
    • withdrawCash(30) in EnterPinState sets the state to WithdrawCashState and immediately re-triggers withdrawCash(30) to dispense the funds.
    • WithdrawCashState decrements the cash, checks the balance, and successfully transitions back to IdleState.

Sample Output

---- ATM Machine Booting UP ----

---- Attempt 1: Direct withdrawing cash ----
ERROR: Enter pin first!

---- Attempt 2: Happy Path ----
Insert Card to withdraw money.
Enter pin to withdraw cash!
Cash is here, please collect!
Success! Dispensing $30. Please collect your cash.
Remaining ATM balance: $20
Enter fullscreen mode Exit fullscreen mode

Top comments (0)