Table of Contents
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
- stateName() string
- addMoney(int) error
- enterAmount(int) error
- enterPin(money int) error
- 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