Object-Oriented Design for an ATM
design-an-atm
Design an ATM
Let's design an ATM.
We'll cover the following:
- System Requirements
- How ATM works?
- Use Case Diagram
- Class Diagram
- Activity Diagrams
- Sequence Diagram
- Code
An automated teller machine (ATM) is an electronic telecommunications instrument that provides the clients of a financial institution with access to financial transactions in a public space without the need for a cashier or bank teller. ATMs are necessary as not all the bank branches are open every day of the week, and some customers may not be in a position to visit a bank each time they want to withdraw or deposit money.
Problem
Design an object-oriented ATM system that supports customer authentication via card and PIN, executing core banking transactions (balance inquiry, cash withdrawal, deposits, transfer), operator maintenance (refill cash/receipts), and managerial reporting, while interacting with a bank backend for account validation and transaction logging.
Solution
1. Requirements Analysis
Functional Requirements:
- Authenticate customer using card + PIN before any transaction.
- Support balance inquiry, cash withdrawal, cash deposit, check deposit, funds transfer.
- Manage two account types: Checking and Savings.
- Dispense cash using appropriate denominations; accept cash/check deposits.
- Print receipts for completed transactions.
- Maintain transaction log and hardware error log.
- Operator can refill cash and consumables (receipts, ink) and remove deposited items.
- Bank manager can generate deposit/withdrawal reports and view ATM cash levels.
- Handle session lifecycle: retain card until session end or cancel.
Non-Functional (implicit in description):
- Reliability: ATM must not shutdown mid-transaction.
- Security: PIN validation; card retained during active session.
- Auditability: Maintain logs for transactions and hardware failures.
2. Use Case Diagram
Actors: Customer, Operator, BankManager, BankSystem. Use Cases: Authenticate, Balance Inquiry, Withdraw Cash, Deposit Cash, Deposit Check, Transfer Funds, Print Receipt, Refill Cash, Generate Reports.
graph TD subgraph ATMSystem[ATM System] UC_Auth(Authenticate) UC_Bal(Balance Inquiry) UC_Wdr(Withdraw Cash) UC_DepCash(Deposit Cash) UC_DepCheck(Deposit Check) UC_Transfer(Transfer Funds) UC_Print(Print Receipt) UC_Refill(Refill Cash & Consumables) UC_Report(Generate Reports) end Customer --> UC_Auth Customer --> UC_Bal Customer --> UC_Wdr Customer --> UC_DepCash Customer --> UC_DepCheck Customer --> UC_Transfer Customer --> UC_Print Operator --> UC_Refill BankManager --> UC_Report BankSystem --> UC_Auth BankSystem --> UC_Wdr BankSystem --> UC_DepCash BankSystem --> UC_DepCheck BankSystem --> UC_Transfer
3. Class Diagram
Core Classes:
- ATM: Coordinates components and session, communicates with Bank.
- CardReader, Keypad, Screen, CashDispenser, DepositSlot (CashDepositSlot, CheckDepositSlot), Printer: Hardware abstraction components.
- Bank: External system interface for account verification and transaction posting.
- Account (CheckingAccount, SavingAccount): Stores balances.
- Customer + Card: Customer identity and authentication medium.
- Transaction (BalanceInquiry, Withdraw, Deposit, CheckDeposit, CashDeposit, Transfer): Encapsulate transaction behavior and data.
classDiagram class ATM { -id: String -location: String +authenticate(card, pin) +makeTransaction(tx: Transaction) } class CardReader { +readCard() } class Keypad { +getInput(): String } class Screen { +showMessage(msg: String) } class CashDispenser { +dispense(amount: double) } class DepositSlot { +getTotalAmount(): double } class CashDepositSlot { +receiveBill() } class CheckDepositSlot { +getCheckAmount(): double } class Printer { +printReceipt(tx: Transaction) } class Bank { +getAccount(accountNumber): Account } class Account { -accountNumber: String; -availableBalance: double; +getAvailableBalance(): double } class CheckingAccount { +debitCardNumber: String } class SavingAccount { +withdrawLimit: double } class Customer { -name: String; -status: CustomerStatus } class Card { -cardNumber: String; -expiry: Date; -pin: String } class Transaction { -transactionId: String; -status: TransactionStatus; +execute() } class BalanceInquiry { } class Withdraw { -amount: double } class Deposit { -amount: double } class Transfer { -destinationAccount: String } ATM --> CardReader ATM --> Keypad ATM --> Screen ATM --> CashDispenser ATM --> DepositSlot ATM --> Printer ATM --> Bank Customer --> Card Card --> Account Account <|-- CheckingAccount Account <|-- SavingAccount Transaction <|-- BalanceInquiry Transaction <|-- Withdraw Transaction <|-- Deposit Transaction <|-- Transfer
4. Activity Diagrams
Activity: Customer Authentication
graph TD A[Insert Card] --> B[Enter PIN] B --> C{Validate PIN with Bank} C -- Valid --> D[Start Session] C -- Invalid --> E[Retry or Retain Card]
Activity: Cash Withdrawal
graph TD W1[Select Withdraw] --> W2[Enter Amount] W2 --> W3{Sufficient Balance?} W3 -- No --> W4[Show Error] W3 -- Yes --> W5{Cash Available?} W5 -- No --> W6[Insufficient Cash] W5 -- Yes --> W7[Dispense Cash] W7 --> W8[Print Receipt]
Activity: Check Deposit
graph TD D1[Select Deposit Check] --> D2[Insert Check] D2 --> D3[Capture Amount] D3 --> D4[Store for Verification] D4 --> D5[Print Receipt]
Activity: Fund Transfer
graph TD T1[Select Transfer] --> T2[Enter Destination] T2 --> T3[Enter Amount] T3 --> T4{Sufficient Balance?} T4 -- No --> T5[Show Error] T4 -- Yes --> T6[Execute Transfer] T6 --> T7[Print Receipt]

5. High-Level Code Implementation
Java
enum TransactionType { BALANCE_INQUIRY, DEPOSIT_CASH, DEPOSIT_CHECK, WITHDRAW, TRANSFER }
enum TransactionStatus { SUCCESS, FAILURE, BLOCKED, FULL, PARTIAL, NONE }
enum CustomerStatus { ACTIVE, BLOCKED, BANNED, COMPROMISED, ARCHIVED, CLOSED, UNKNOWN }
class Address { String street, city, state, zip, country; }
class Card { String cardNumber; String customerName; String expiry; String pin; }
class Account { String accountNumber; double totalBalance; double availableBalance; double getAvailableBalance(){ return availableBalance; } }
class CheckingAccount extends Account { String debitCardNumber; }
class SavingAccount extends Account { double withdrawLimit; }
abstract class Transaction { String id; TransactionStatus status; abstract void execute(); }
class BalanceInquiry extends Transaction { String accountId; void execute(){} }
class Deposit extends Transaction { double amount; void execute(){} }
class Withdraw extends Transaction { double amount; void execute(){} }
class Transfer extends Transaction { String destinationAccount; double amount; void execute(){} }
class CashDispenser { int totalFiveDollarBills; int totalTwentyDollarBills; void dispenseCash(double amount){} }
class Keypad { String getInput(){ return ""; } }
class Screen { void showMessage(String msg){} }
class Printer { void printReceipt(Transaction tx){} }
class DepositSlot { double totalAmount; double getTotalAmount(){ return totalAmount; } }
class CashDepositSlot extends DepositSlot { void receiveDollarBill(){} }
class CheckDepositSlot extends DepositSlot { double getCheckAmount(){ return 0.0; } }
class ATM { String atmId; String location; CashDispenser cashDispenser; Keypad keypad; Screen screen; Printer printer; void authenticateUser(Card card, String pin){} void makeTransaction(Transaction tx){} }
class Bank { String name; String bankCode; Account getAccount(String number){ return null; } }
class Customer { String name; Address address; String email; String phone; CustomerStatus status; Card card; Account account; void makeTransaction(Transaction tx){} }
Python
from enum import Enum
from typing import Optional
class TransactionType(Enum):
BALANCE_INQUIRY = 1; DEPOSIT_CASH = 2; DEPOSIT_CHECK = 3; WITHDRAW = 4; TRANSFER = 5
class TransactionStatus(Enum):
SUCCESS = 1; FAILURE = 2; BLOCKED = 3; FULL = 4; PARTIAL = 5; NONE = 6
class CustomerStatus(Enum):
ACTIVE = 1; BLOCKED = 2; BANNED = 3; COMPROMISED = 4; ARCHIVED = 5; CLOSED = 6; UNKNOWN = 7
class Address:
def __init__(self, street: str, city: str, state: str, zip_code: str, country: str) -> None:
self.street = street; self.city = city; self.state = state; self.zip_code = zip_code; self.country = country
class Card:
def __init__(self, number: str, customer_name: str, expiry: str, pin: str) -> None:
self.card_number = number; self.customer_name = customer_name; self.expiry = expiry; self.pin = pin
class Account:
def __init__(self, account_number: str) -> None:
self.account_number = account_number; self.total_balance = 0.0; self.available_balance = 0.0
def get_available_balance(self) -> float:
return self.available_balance
class CheckingAccount(Account):
def __init__(self, account_number: str, debit_card_number: str) -> None:
super().__init__(account_number); self.debit_card_number = debit_card_number
class SavingAccount(Account):
def __init__(self, account_number: str, withdraw_limit: float) -> None:
super().__init__(account_number); self.withdraw_limit = withdraw_limit
class Transaction:
def __init__(self, id: str, status: TransactionStatus) -> None:
self.id = id; self.status = status
def execute(self) -> None: pass
class BalanceInquiry(Transaction):
def __init__(self, id: str, account_id: str) -> None:
super().__init__(id, TransactionStatus.NONE); self.account_id = account_id
def execute(self) -> None: pass
class Deposit(Transaction):
def __init__(self, id: str, amount: float) -> None:
super().__init__(id, TransactionStatus.NONE); self.amount = amount
def execute(self) -> None: pass
class Withdraw(Transaction):
def __init__(self, id: str, amount: float) -> None:
super().__init__(id, TransactionStatus.NONE); self.amount = amount
def execute(self) -> None: pass
class Transfer(Transaction):
def __init__(self, id: str, destination_account: str, amount: float) -> None:
super().__init__(id, TransactionStatus.NONE); self.destination_account = destination_account; self.amount = amount
def execute(self) -> None: pass
class CashDispenser:
def __init__(self) -> None:
self.total_five_dollar_bills = 0; self.total_twenty_dollar_bills = 0
def dispense_cash(self, amount: float) -> None: pass
class Keypad:
def get_input(self) -> str: return ""
class Screen:
def show_message(self, message: str) -> None: pass
class Printer:
def print_receipt(self, tx: Transaction) -> None: pass
class DepositSlot:
def __init__(self) -> None:
self.total_amount = 0.0
def get_total_amount(self) -> float: return self.total_amount
class CashDepositSlot(DepositSlot):
def receive_dollar_bill(self) -> None: pass
class CheckDepositSlot(DepositSlot):
def get_check_amount(self) -> float: return 0.0
class ATM:
def __init__(self, atm_id: str, location: str) -> None:
self.atm_id = atm_id; self.location = location
self.cash_dispenser = CashDispenser(); self.keypad = Keypad(); self.screen = Screen(); self.printer = Printer()
def authenticate_user(self, card: Card, pin: str) -> bool: return True
def make_transaction(self, tx: Transaction) -> None: pass
class Bank:
def __init__(self, name: str, bank_code: str) -> None:
self.name = name; self.bank_code = bank_code
def get_account(self, account_number: str) -> Optional[Account]: return None
class Customer:
def __init__(self, name: str, address: Address, email: str, phone: str, status: CustomerStatus, card: Card, account: Account) -> None:
self.name = name; self.address = address; self.email = email; self.phone = phone; self.status = status; self.card = card; self.account = account
def make_transaction(self, tx: Transaction) -> None: pass
Source https://github.com/tssovi/grokking-the-object-oriented-design-interview https://medium.com/swlh/atm-an-object-oriented-design-e3a2435a0830 class CustomerStatus(Enum): ACTIVE, BLOCKED, BANNED, COMPROMISED, ARCHIVED, CLOSED, UNKNOWN = 1, 2, 3, 4, 5, 6, 7
class Address: def init(self, street, city, state, zip_code, country): self.__street_address = street self.__city = city self.__state = state self.__zip_code = zip_code self.__country = country
**Customer, Card, and Account:** “Customer” encapsulates the ATM user, “Card” the ATM card, and “Account” can be of two types: checking and savings:
```python
# For simplicity, we are not defining getter and setter functions. The reader can
# assume that all class attributes are private and accessed through their respective
# public getter methods and modified only through their public methods function.
class Customer:
def __init__(self, name, address, email, phone, status):
self.__name = name
self.__address = address
self.__email = email
self.__phone = phone
self.__status = status
self.__card = Card()
self.__account = Account
def make_transaction(self, transaction):
None
def get_billing_address(self):
None
class Card:
def __init__(self, number, customer_name, expiry, pin):
self.__card_number = number
self.__customer_name = customer_name
self.__card_expiry = expiry
self.__pin = pin
def get_billing_address(self):
None
class Account:
def __init__(self, account_number):
self.__account_number = account_number
self.__total_balance = 0.0
self.__available_balance = 0.0
def get_available_balance(self):
return self.__available_balance
class SavingAccount(Account):
def __init__(self, withdraw_limit):
self.__withdraw_limit = withdraw_limit
class CheckingAccount(Account):
def __init__(self, debit_card_number):
self.__debit_card_number = debit_card_number
Bank, ATM, CashDispenser, Keypad, Screen, Printer and DepositSlot: The ATM will have different components like keypad, screen, etc.
from abc import ABC
class Bank:
def __init__(self, name, bank_code):
self.__name = name
self.__bank_code = bank_code
def get_bank_code(self):
return self.__bank_code
def add_atm(self, atm):
None
class ATM:
def __init__(self, id, location):
self.__atm_id = id
self.__location = location
self.__cash_dispenser = CashDispenser()
self.__keypad = Keypad()
self.__screen = Screen()
self.__printer = Printer()
self.__check_deposit = CheckDeposit()
self.__cash_deposit = CashDeposit
def authenticate_user(self):
None
def make_transaction(self, customer, transaction):
None
class CashDispenser:
def __init__(self):
self.__total_five_dollar_bills = 0
self.__total_twenty_dollar_bills = 0
def dispense_cash(self, amount):
None
def can_dispense_cash(self):
None
class Keypad:
def get_input(self):
None
class Screen:
def show_message(self, message):
None
def get_input(self):
None
class Printer:
def print_receipt(self, transaction):
None
class CheckDeposit:
def __init__(self):
None
class CashDeposit:
def __init__(self):
None
class DepositSlot(ABC):
def __init__(self):
self.__total_amount = 0.0
def get_total_amount(self):
return self.__total_amount
class CheckDepositSlot(DepositSlot):
def get_check_amount(self):
None
class CashDepositSlot(DepositSlot):
def receive_dollar_bill(self):
None
Transaction and its subclasses: Customers can perform different transactions on the ATM, these classes encapsulate them:
from abc import ABC
class Transaction(ABC):
def __init__(self, id, creation_date, status):
self.__transaction_id = id
self.__creation_time = creation_date
self.__status = status
def make_transation(self):
None
class BalanceInquiry(Transaction):
def __init__(self, account_id):
self.__account_id = account_id
def get_account_id(self):
return self.__account_id
class Deposit(Transaction):
def __init__(self, amount):
self.__amount = amount
def get_amount(self):
return self.__amount
class CheckDeposit(Deposit):
def __init__(self, check_number, bank_code):
self.__check_number = check_number
self.__bank_code = bank_code
def get_check_number(self):
return self.__check_number
class CashDeposit(Deposit):
def __init__(self, cash_deposit_limit):
self.__cash_deposit_limit = cash_deposit_limit
class Withdraw(Transaction):
def __init__(self, amount):
self.__amount = amount
def get_amount(self):
return self.__amount
class Transfer(Transaction):
def __init__(self, destination_account_number):
self.__destination_account_number = destination_account_number
def get_destination_account(self):
return self.__destination_account_number
Source https://github.com/tssovi/grokking-the-object-oriented-design-interview complete from: https://medium.com/swlh/atm-an-object-oriented-design-e3a2435a0830