Master Practical Java Architecture & Design Patterns!
Subscribe to Ram N Java for clean coding tutorials, GoF design patterns, and real-world system implementations!
🔔 Subscribe to Ram N JavaThe ATM Lifecycle Problem
An Automated Teller Machine (ATM) is a classic example of a system driven by complex, sequential state transitions. An ATM moves through multiple operational phases:
- No Card Inserted: Awaits a user to insert a debit/credit card while rejecting transaction requests.
- Card Inserted / PIN Pending: Prompts the user for a secure PIN while locking the card in place.
- PIN Validated: Authorizes account access, allows cash withdrawal or balance inquiries.
- Dispensing Cash: Validates machine cash reserves, updates account balance, and releases currency.
- Out of Cash: Disables withdrawal operations while allowing non-cash transactions or displaying service alerts.
If you implement an ATM system using traditional procedural logic with nested if-else or switch-case branches, handling edge cases—such as ejecting a card mid-transaction or running out of currency—quickly turns the codebase into an unmaintainable, error-prone mess.
Architecture of the State Pattern Solution
The State Design Pattern solves this by encapsulating each ATM state into a dedicated class, allowing the ATM context to change its behavior dynamically at runtime:
-
State Interface (ATMState): Defines standard ATM interactions, including
insertCard(),ejectCard(),enterPin(), andwithdrawCash(). -
Concrete State Classes: Classes like
NoCardState,HasCardState,HasCorrectPinState, andNoCashStateimplement behavior specific to that stage and control transitions to the next appropriate state. - Context Class (ATMMachine): Manages physical resources (e.g., cash inventory, card reader), keeps a reference to the active state, and delegates incoming customer operations directly to that state object.
Key Architectural Benefits
Applying the State pattern to mission-critical financial systems like an ATM provides significant software design advantages:
- Elimination of Spaghetti Conditionals: Removes convoluted conditional checks, replacing them with clean polymorphic method invocations.
- Single Responsibility Principle: Each state’s validation logic, business rules, and transition rules are isolated within its own dedicated class.
- Open/Closed Principle: Adding new states or transaction types (such as biometric authentication or maintenance lock) does not break existing state classes.
Related Tutorials from Ram N Java
Click here to watch in Youtube : https://www.youtube.com/watch?v=97AmoYlWxrI
| State Design pattern - Implementation [ATM] |
| State Design pattern - Implementation [ATM] - Class Diagram |
public interface AtmMachineState
{
public void insertDebitCard();
public void ejectDebitCard();
public void enterPinAndWithdrawMoney();
}
HasDebitCardState .java
public class HasDebitCardState implements AtmMachineState
{
@Override
public void insertDebitCard()
{
System.out.println("Debit Card is already there,So you cannot insert the Debit Card ...");
}
@Override
public void ejectDebitCard()
{
System.out.println("Debit Card is ejected...");
}
@Override
public void enterPinAndWithdrawMoney()
{
System.out.println("Pin number has been entered correctly and money has been withdrawn...");
}
}
NoDebitCardState.java
public class NoDebitCardState implements AtmMachineState
{
@Override
public void insertDebitCard()
{
System.out.println("DebitCard inserted ....");
}
@Override
public void ejectDebitCard()
{
System.out.println("No Debit Card in ATM Machine slot, so you cannot eject the Debit Card...");
}
@Override
public void enterPinAndWithdrawMoney()
{
System.out.println("No Debit Card in ATM Machine slot, so you cannot enter the pin and withdraw the money...");
}
}
AtmMachine.java
// This is like Context
public class AtmMachine implements AtmMachineState
{
private AtmMachineState atmMachineState;
public AtmMachine()
{
atmMachineState = new NoDebitCardState();
}
public AtmMachineState getAtmMachineState()
{
return atmMachineState;
}
public void setAtmMachineState( AtmMachineState atmMachineState )
{
this.atmMachineState = atmMachineState;
}
@Override
public void insertDebitCard()
{
atmMachineState.insertDebitCard();
/*
* Debit Card has been inserted so internal state of ATM Machine
* has been changed to 'hasDebitCardState'
*/
if( atmMachineState instanceof NoDebitCardState )
{
AtmMachineState hasDebitCardState = new HasDebitCardState();
setAtmMachineState(hasDebitCardState);
System.out.println("ATM Machine internal state has been moved to : "
+ atmMachineState.getClass().getName());
}
}
@Override
public void ejectDebitCard()
{
atmMachineState.ejectDebitCard();
/*
* Debit Card has been ejected so internal state of ATM Machine
* has been changed to 'noDebitCardState'
*/
if( atmMachineState instanceof HasDebitCardState )
{
AtmMachineState noDebitCardState = new NoDebitCardState();
setAtmMachineState(noDebitCardState);
System.out.println("ATM Machine internal state has been moved to : "
+ atmMachineState.getClass().getName());
}
}
@Override
public void enterPinAndWithdrawMoney()
{
atmMachineState.enterPinAndWithdrawMoney();
}
}
Client.java
public class Client
{
public static void main( String[] args )
{
/*
* Initially ATM Machine in 'noDebitCardState'
*/
AtmMachine atmMachine = new AtmMachine();
System.out.println("ATM Machine Current state : "
+ atmMachine.getAtmMachineState().getClass().getName());
System.out.println();
atmMachine.enterPinAndWithdrawMoney();
atmMachine.ejectDebitCard();
atmMachine.insertDebitCard();
System.out.println("\n*******************************************************");
/*
* Debit Card has been inserted so internal state of ATM Machine
* has been changed to 'hasDebitCardState'
*/
System.out.println("\nATM Machine Current state : "
+ atmMachine.getAtmMachineState().getClass().getName());
System.out.println();
atmMachine.enterPinAndWithdrawMoney();
atmMachine.insertDebitCard();
atmMachine.ejectDebitCard();
System.out.println("\n*******************************************************");
/*
* Debit Card has been ejected so internal state of ATM Machine
* has been changed to 'noDebitCardState'
*/
System.out.println("\nATM Machine Current state : "
+ atmMachine.getAtmMachineState().getClass().getName());
}
}
Output
ATM Machine Current state : NoDebitCardState
No Debit Card in ATM Machine slot, so you cannot enter the pin and withdraw the money...
No Debit Card in ATM Machine slot, so you cannot eject the Debit Card...
DebitCard inserted ....
ATM Machine internal state has been moved to : HasDebitCardState
*******************************************************
ATM Machine Current state : HasDebitCardState
Pin number has been entered correctly and money has been withdrawn...
Debit Card is already there,So you cannot insert the Debit Card ...
Debit Card is ejected...
ATM Machine internal state has been moved to : NoDebitCardState
*******************************************************
ATM Machine Current state : NoDebitCardState
No comments:
Post a Comment