Skip to content

Welcome to Tech by Example

Menu
  • Home
  • Posts
  • System Design Questions
Menu

ATM Machine System Design

Posted on November 26, 2021January 10, 2022 by admin

Table of Contents

  • Overview
  • UML Diagram
  • Low–Level Design
  • Program
  • Conclusion

Overview

The objective is to design an ATM machine. Please note that designing an ATM Machine is an Object-Oriented Problem. It is not a distributed system problem. So we need to approach it that way. Below are the requirements

  • An ATM machine will allow the admin or bank to add the money
  • An ATM machine will allow users to enter the amount
  • Once the amount is entered the user can enter the pin. The cash will be dispensed after the pin is entered and is correct
  • An ATM machine will have different states.

Also for simplicity let’s assume that an ATM Machine can be in 4 different states

  • noMoney – This is the initial state when ATM has no money and hence it cannot accept the withdrawal of money.
  • hasMoney – This is the next state when ATM is ready to accept the withdrawal of money.
  • amountEntered – This is the state after which the amount is entered by the user. At this state, it will allow the user to enter the amount
  • pinEntered – This is the state when the user has entered the pin. If the pin is correct then the cash will be dispensed

An ATM machine will also have different actions. Again for simplicity lets assume that there are only four actions:

  • Add money
  • Enter amount
  • Enter Pin
  • Dispense Cash

We can use a state design pattern here to design the ATM machine. State design pattern is a behavioral design pattern that is based on a Finite State Machine.

Now the question is why we are using a State Design Pattern to design an ATM Machine? Below are two reasons

  • State design pattern is used when an object can be in many different states. Depending upon the current request the object needs to change its current state
    • ATM machines can be in many different states. An ATM Machine will move from one state to another. Let’s say ATM Machine is in amountEntered then it will move to pinEntered state once the action “Enter Pin” is done
  • State design pattern is used when an object will have different responses to the same request depending upon the current state. Using state design pattern here will prevent a lot of conditional statements
    • For example in the case of an ATM Machine, if a user wants to dispense cash then the machine will proceed if it is in hasMoney or it will reject if it is in noMoney. If you notice here that the ATM Machine on the request of withdrawal gives two different responses depending upon whether it is in hasMoney or noMoney.  With this approach, our code will not have any kind of conditional statement. All the logic is being handled by concrete state implementations.

UML Diagram

Below is the UML diagram of the ATM Machine

Here is the overall idea

  • We will have an interface “State” which defines signatures of functions that represents action in the context of an ATM Machine. Below are the actions function signatures
    1. stateName() string
    2. addMoney(int) error
    3. enterAmount(int) error
    4. enterPin(money int) error
    5. dispenseCash() error
  • Each of the concrete states implements all 5 above functions and either move to another state on these actions or gives some response.
  • Each of the concrete states also embeds a pointer to the current ATM Machine object so that state transition can invoke some calls on the ATM Machine Object.

Low–Level Design

Below is the low-level design expressed in the Go programming language.

Later we will see a working example as well

ATM Machine Class

type atmMachine struct {
	hasMoney      state
	noMoney       state
	amountEntered state
	pinEntered    state

	currentState state

	totalMoney int
}

func newATMMachine(totalMoney int) *atmMachine {
	a := &atmMachine{
		totalMoney: totalMoney,
	}
	hasMoneyState := &hasMoneyState{
		atmMachine: a,
	}
	noMoneyState := &noMoneyState{
		atmMachine: a,
	}
	amountEnteredState := &amountEnteredState{
		atmMachine: a,
	}
	pinEnteredState := &pinEnteredState{
		atmMachine: a,
	}

	a.setState(hasMoneyState)
	a.hasMoney = hasMoneyState
	a.noMoney = noMoneyState
	a.amountEntered = amountEnteredState
	a.pinEntered = pinEnteredState
	return a
}

func (v *atmMachine) addMoney(money int) error 

func (v *atmMachine) enterAmount(money int) error 

func (v *atmMachine) enterPin(money int) error 

func (v *atmMachine) dispenseCash(money int) error 

func (v *atmMachine) setState(s state) 

func (v *atmMachine) incrementMoney(money int)

func (v *atmMachine) decrementMoney(money int) 

func (v *atmMachine) checkAvailability(money int) error 

func (v *atmMachine) verifyPin(pin int) error

State Interface

type state interface {
	stateName() string
	addMoney(int) error
	enterAmount(int) error
	enterPin(int) error
	dispenseCash(int) error
}

NoMoney State Class

type noMoneyState struct {
	atmMachine *atmMachine
}

func (i *noMoneyState) stateName() string 

func (i *noMoneyState) addMoney(money int) error

func (i *noMoneyState) enterAmount(money int) error 

func (i *noMoneyState) enterPin(pin int) error 

func (i *noMoneyState) dispenseCash(money int) error 

Has Money State Class

type hasMoneyState struct {
	atmMachine *atmMachine
}

func (i *hasMoneyState) stateName() string 

func (i *hasMoneyState) addMoney(money int) error 

func (i *hasMoneyState) enterAmount(money int) error 

func (i *hasMoneyState) enterPin(pin int) error 

func (i *hasMoneyState) dispenseCash(money int) error 

Amount Entered State Class

type amountEnteredState struct {
	atmMachine *atmMachine
}

func (i *amountEnteredState) stateName() string 

func (i *amountEnteredState) addMoney(money int) error 

func (i *amountEnteredState) enterAmount(money int) error 

func (i *amountEnteredState) enterPin(pin int) error 

func (i *amountEnteredState) dispenseCash(money int) error

Pin Entered State Class

type pinEnteredState struct {
	atmMachine *atmMachine
}

func (i *pinEnteredState) stateName() string 

func (i *pinEnteredState) addMoney(money int) error 

func (i *pinEnteredState) enterAmount(money int) error 

func (i *pinEnteredState) enterPin(pin int) error 

func (i *pinEnteredState) dispenseCash(money int) error

Program

Here is the full working code if anyone is interested in the Go programming language

atmMachine.go

package main

import "fmt"

type atmMachine struct {
	hasMoney      state
	noMoney       state
	amountEntered state
	pinEntered    state

	currentState state

	totalMoney int
}

func newATMMachine(totalMoney int) *atmMachine {
	a := &atmMachine{
		totalMoney: totalMoney,
	}
	hasMoneyState := &hasMoneyState{
		atmMachine: a,
	}
	noMoneyState := &noMoneyState{
		atmMachine: a,
	}
	amountEnteredState := &amountEnteredState{
		atmMachine: a,
	}
	pinEnteredState := &pinEnteredState{
		atmMachine: a,
	}

	a.setState(hasMoneyState)
	a.hasMoney = hasMoneyState
	a.noMoney = noMoneyState
	a.amountEntered = amountEnteredState
	a.pinEntered = pinEnteredState
	return a
}

func (v *atmMachine) addMoney(money int) error {
	return v.currentState.addMoney(money)
}

func (v *atmMachine) enterAmount(money int) error {
	return v.currentState.enterAmount(money)
}

func (v *atmMachine) enterPin(money int) error {
	return v.currentState.enterPin(money)
}
func (v *atmMachine) dispenseCash(money int) error {
	return v.currentState.dispenseCash(money)
}

func (v *atmMachine) setState(s state) {
	v.currentState = s
}
func (v *atmMachine) incrementMoney(money int) {
	fmt.Printf("Adding %d money:\n", money)
	v.totalMoney = v.totalMoney + money
}

func (v *atmMachine) decrementMoney(money int) {
	fmt.Printf("Dispensing %d cash:\n", money)
	v.totalMoney = v.totalMoney - money
}

func (v *atmMachine) checkAvailability(money int) error {
	fmt.Printf("Checking Availability\n")
	if money < v.totalMoney {
		return nil
	}
	return fmt.Errorf("Not enough money")
}

func (v *atmMachine) verifyPin(pin int) error {
	fmt.Println("Verifying Pin")
	//Pin is always true
	return nil
}

state.go

package main

type state interface {
	stateName() string
	addMoney(int) error
	enterAmount(int) error
	enterPin(int) error
	dispenseCash(int) error
}

noMoneyState.go

package main

import "fmt"

type noMoneyState struct {
	atmMachine *atmMachine
}

func (i *noMoneyState) stateName() string {
	return "noMoneyState"
}

func (i *noMoneyState) addMoney(money int) error {
	fmt.Errorf("Add money in progress")
	i.atmMachine.incrementMoney(money)
	return nil
}

func (i *noMoneyState) enterAmount(money int) error {
	return fmt.Errorf("Add money first")
}

func (i *noMoneyState) enterPin(pin int) error {
	return fmt.Errorf("Add money first")
}
func (i *noMoneyState) dispenseCash(money int) error {
	return fmt.Errorf("Add money first")
}

hasMoneyState.go

package main

import "fmt"

type hasMoneyState struct {
	atmMachine *atmMachine
}

func (i *hasMoneyState) stateName() string {
	return "hasMoneyState"
}

func (i *hasMoneyState) addMoney(money int) error {
	fmt.Errorf("Add money in progress")
	i.atmMachine.incrementMoney(money)
	return nil
}

func (i *hasMoneyState) enterAmount(money int) error {
	fmt.Errorf("Amount is entered. Amount:%n", money)
	err := i.atmMachine.checkAvailability(money)
	if err != nil {
		return err
	}
	i.atmMachine.setState(i.atmMachine.amountEntered)
	return nil
}

func (i *hasMoneyState) enterPin(pin int) error {
	return fmt.Errorf("First enter the amount")
}
func (i *hasMoneyState) dispenseCash(money int) error {
	return fmt.Errorf("First enter the amount")
}

amountEnteredState.go

package main

import "fmt"

type amountEnteredState struct {
	atmMachine *atmMachine
}

func (i *amountEnteredState) stateName() string {
	return "amountEnteredState"
}

func (i *amountEnteredState) addMoney(money int) error {
	return fmt.Errorf("Dispensing process in progress")
}

func (i *amountEnteredState) enterAmount(money int) error {
	return fmt.Errorf("Amount already entered")
}

func (i *amountEnteredState) enterPin(pin int) error {
	err := i.atmMachine.verifyPin(pin)
	if err != nil {
		return err
	}
	i.atmMachine.setState(i.atmMachine.pinEntered)
	return nil
}
func (i *amountEnteredState) dispenseCash(money int) error {
	return fmt.Errorf("First Enter Pin")
}

pinEnteredState.go

package main

import "fmt"

type pinEnteredState struct {
	atmMachine *atmMachine
}

func (i *pinEnteredState) stateName() string {
	return "pinEnteredState"
}

func (i *pinEnteredState) addMoney(money int) error {
	return fmt.Errorf("Dispensing process in progress")
}

func (i *pinEnteredState) enterAmount(money int) error {
	return fmt.Errorf("Amount and pin already entered")
}

func (i *pinEnteredState) enterPin(pin int) error {
	return fmt.Errorf("Pin already entered")
}
func (i *pinEnteredState) dispenseCash(money int) error {
	i.atmMachine.decrementMoney(money)
	i.atmMachine.setState(i.atmMachine.hasMoney)
	return nil
}

main.go

package main

import (
	"fmt"
	"log"
)

func main() {
	atmMachine := newATMMachine(100)

	fmt.Println("<<<<<First Transactin: Withdrawing amount 10>>>> ")

	fmt.Printf("ATM current state %s\n\n", atmMachine.currentState.stateName())
	err := atmMachine.enterAmount(10)
	if err != nil {
		log.Fatalf(err.Error())

	}
	fmt.Printf("Amount Entered: %d\n", 10)
	fmt.Printf("Atm Total Money: %d\n", atmMachine.totalMoney)
	fmt.Printf("ATM current state %s\n\n", atmMachine.currentState.stateName())

	err = atmMachine.enterPin(1234)
	if err != nil {
		log.Fatalf(err.Error())
	}
	fmt.Printf("Pin Entered: %d\n", 10)
	fmt.Printf("ATM Total Money: %d\n", atmMachine.totalMoney)
	fmt.Printf("ATM current state %s\n\n", atmMachine.currentState.stateName())

	err = atmMachine.dispenseCash(10)
	if err != nil {
		log.Fatalf(err.Error())
	}
	fmt.Printf("Dispense Cash: %d\n", 10)
	fmt.Printf("ATM Total Money: %d\n", atmMachine.totalMoney)
	fmt.Printf("ATM current state %s\n\n", atmMachine.currentState.stateName())

	fmt.Println()

	fmt.Println("<<<<<Second Transactin: Admin adding 50>>>>")
	err = atmMachine.addMoney(50)
	if err != nil {
		log.Fatalf(err.Error())
	}
	fmt.Printf("Atm Total Money: %d\n", atmMachine.totalMoney)
	fmt.Printf("ATM current state %s\n\n", atmMachine.currentState.stateName())

	fmt.Println("<<<<<Third Transaction. Withdrawing amount 200>>>>")
	err = atmMachine.enterAmount(200)
	if err != nil {
		log.Fatalf(err.Error())

	}
}

Output:

<<<<<First Transactin: Withdrawing amount 10>>>> 
ATM current state hasMoneyState

Checking Availability
Amount Entered: 10
Atm Total Money: 100
ATM current state amountEnteredState

Verifying Pin
Pin Entered: 10
ATM Total Money: 100
ATM current state pinEnteredState

Dispensing 10 cash:
Dispense Cash: 10
ATM Total Money: 90
ATM current state hasMoneyState


<<<<<Second Transactin: Admin adding 50>>>>
Adding 50 money:
Atm Total Money: 140
ATM current state hasMoneyState

<<<<<Third Transaction. Withdrawing amount 200>>>>
Checking Availability
2021/11/26 18:29:26 Not enough money
exit status 1

Conclusion

This is all about designing an ATM Machine. Hope you have liked this article. Please share feedback in the comments

  • atm
  • system
  • ©2025 Welcome to Tech by Example | Design: Newspaperly WordPress Theme