problemhardoodatm-ood-designatm ood designatmooddesign

Object-Oriented Design for an ATM

design-an-atm

Design an ATM

Let's design an ATM.

We'll cover the following:

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

ATM Use Case Diagram

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

ATM Class Diagram ATM UML

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]

ATM Customer Authentication Activity Diagram

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]

ATM Cash Withdraw Activity Diagram

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]

ATM Deposit Check Activity Diagram

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]

ATM Fund Transfer Activity Diagram

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

ATM ATM Sequence Diagram

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

Comments