ATM — An Object-Oriented Design

Nitish Sharma
The Startup
Published in
5 min readNov 30, 2020

As a learning exercise, I have decided to go on walks (in the outside world) to kill boredom, pick one “real world thing” and redesign it in the object oriented way to the best of my abilities, as a series of articles.

To put it plainly, the object-orient­ed paradigm is one that is based on the idea that everything that exists has some data and exhibits certain behaviour based on that data.

Today’s walk was more of a mundane chore, as I had run out of groceries. But, the fact that the local grocer only accepted cash made it interesting; I ended up having to withdraw money from an ATM.

Automated Teller Machine

An ATM is a device that allows bank account holders possessing debit cards to perform basic, quick, self-service financial transactions at any time and without requiring to interact with the bank staff.

An ATM User Interface

Requirements

With the description above serving as our problem statement, we can pin down the exact requirements:

  • Each user has one bank account at the bank
  • User should be able to insert card and perform transactions
  • ATM should authenticate user on the basis of the PIN entered
  • Once authenticated user should be able transactions, namely view account balance, deposit cash and withdraw cash
  • User can only perform one transaction at any given time
  • At the end of a transaction, appropriate messages should be displayed to the user to communicate the success/failure of the transaction
  • At the end of a transaction, user should be able to start another transaction
  • Card should be returned when user exits

Lets get crackin’

Clearly the use of an ATM involves 2 entities, User and the ATM itself. These are our actors. The user gives an instruction to the ATM and the ATM takes the appropriate action.

Let’s look at a simple use case.

If I need Rs. 1000 worth of cash, I go to the nearest ATM, insert my card, enter the correct PIN, select the option of withdrawal, enter the digits 1000, wait for the machine to dispense cash, collect the dispensed cash from the drawer, look at the remaining balance displayed on screen and since I don’t have any other business, I exit.

If I wanted to deposit some cash after that withdrawal, there would be another set of events happening.

Would designing this system require us to feed each individual possible workflows into it? No.

We need to keep track of this events, for which we can define different states of the ATM. Depending on the event that occurs, the ATM can transition to a state.

So let’s pen down the possible states that an ATM can be in at any given time:

  • READY — Ready to accept card
  • ENTER PIN — Wait for the user to enter PIN, once they have inserted the card
  • SELECT TRANSACTION — Wait for the user to select a transaction
  • DEPOSIT — Wait for user to insert cash, once they have selected the deposit cash option
  • WITHDRAW — Wait for user to input required amount, once they have selected the withdraw cash option
  • DISPLAY BALANCE — Display account balance once the transaction is over and wait for the user to decide the next transaction or exit
  • CASH DISPENSED — Dispense cash once the transaction is over and wait for the user to collect it
  • ERROR MESSAGE DISPLAYED — Display error message if the ATM has insufficient cash or user’s account has insufficient balance and wait the user to decide the next transaction or exit
  • INVALID CASH RETURNED — Return invalid currency if any found once the transaction is over and wait for the user to collect it
  • EXIT MESSAGE DISPLAYED — Display exit message once user exits and return the card

Based on the states listed above I created the following state diagram.

ATM State Diagram

Through this finite-state machine model, instead thinking of all the possible workflows, we have recorded all the possible state transitions and can now build an ATM!

Classes and Objects

User and ATM are the main entities involved here.

But on taking another look at the requirements, we can observe that User has additional information associated to it, such as a Bank Account and a Card. User may possess some Cash as well.

So classes for our design will be:

  • ATM
  • User
  • Account
  • Card
  • Cash

The Code

User must have an account and a card

User class

Account has a balance which should be updated when a transaction occurs

Account class

Card should get authenticated based on PIN entered

Card class

User may possess some Cash to deposit. Some of this cash could be damaged or fake

Cash class

And now the ATM class based on the state diagram above

ATM class

The ATM class exposes all the methods required user interaction. A modular approach has been taken to define different entities. Looks SOLID? Not really.

  • Let’s say a new requirement for the ATM to allow money transfer comes in. The current design will require modification in the ATM class; addition or modification of bunch of states and methods handling the transition b/w them as well as the existing states. This violates the Open-closed principle
  • State-related logic is embedded in the ATM class which violates the Single-responsibility principle.
  • Since ATM class functions as a finite-state machine, we can incorporate the State design pattern

State pattern

The State pat­tern sug­gests that we cre­ate new class­es for all pos­si­ble states of an object and extract all state-spe­cif­ic behav­iours into these classes.

State pattern UML diagram
  • Context class stores a reference to the concrete state class which does the state related work.
  • State inter­face declares the state-spe­cif­ic meth­ods.
  • Con­crete States pro­vide their own imple­men­ta­tions for the state-spe­cif­ic meth­ods
  • For every state-transition, the reference to the specific state class is modified.

Refactor ATM

We can declare a state interface ATMState to expose the following state-spe­cif­ic methods

read_inserted_card
authenticate_pin
select_transaction
deposit_cash
invalid_cash_returned
enter_withdrawal_amount
cash_dispensed
display_balance
transact_again
exit
return_card
ATMState class serving as an interface

Then create the subsequent concrete states inheriting ATMState and override the appropriate methods

Each state as a concrete state class

Finally, refactor the ATM class to serve as context

A much cleaner ATM as context class

ATM class will delegate all the actions that it receives to the specific state classes. The individual states will process the command and perform a state transition by resetting the state in the context.

With this overhaul, coupling of states is eliminated and a new state can be easily defined and plugged into the existing implementation with minimal change.

References

--

--