Requirement Gathering (to be given by the interviewer)
Cash Deposit
Cash Withdrawl
Pin Change
Check Balance
User Flow (to be confirmed by the interviewer)
Card Insertion: You insert your ATM card into the card reader slot.
Card Authentication: The ATM's card reader reads the data on your card's magnetic stripe or the chip, which includes your account information, card number, and security data.
PIN Entry: The ATM prompts you to enter your Personal Identification Number (PIN), a secret code that you've set up to access your account.
PIN Validation: The ATM verifies the entered PIN against the one associated with the card. If the PIN is correct, it proceeds to the next step. If the PIN is incorrect after a certain number of tries, the ATM might block your card for security reasons.
Transaction Selection: The ATM presents a menu of available transactions, which typically include options like cash withdrawal, balance inquiry, funds transfer, bill payment, and more. You select the desired transaction.
Transaction Processing: After selecting a transaction, the ATM communicates with the bank's host system or network. It sends a request for the chosen transaction along with your card and PIN information.
Authorization: The bank's host system verifies the transaction request. It checks your account balance, transaction limits, and whether the card is valid and not reported as lost or stolen. If everything checks out, the bank authorizes the transaction.
Cash Dispensing (for Withdrawals): If you're making a cash withdrawal, and it's approved, the ATM dispenses the requested amount in the denominations you've chosen.
Receipt and Confirmation: The ATM prints a transaction receipt, which includes details of the transaction, your account balance (if requested), and a record of the withdrawal. You can choose to receive or decline the receipt.
Card Removal: The ATM prompts you to remove your card. It's essential to take your card to ensure the transaction is complete and your account is secure.
End of Transaction: The ATM session ends. If you have more transactions, you can perform additional operations or choose to exit.
Machine Reset: After a certain idle period, the ATM resets and clears any sensitive data to ensure the next user's privacy and security.
Designing Classes/Objects/Relationships
- We can deduce from the flow that the ATM has a set of states(ex IDLE, CARD_VALIDATED, INVALID_CARD) and triggers (ex CARD_INSERTED, PIN_AUTHENTICATED ), we can model these using the state design pattern
STATE | POSSIBLE TRIGGER'S | POSSIBLE NEXT STATE'S |
IDLE | CARD_INSRTED,EXIT | CARD_VALIDATED, INVALID-CARD, IDLE |
CARD_VALIDATED | INSERT_PIN,EXIT | PIN_AUTHENTICATED, INVALID_PIN_INSRTED, IDLE |
PIN_AUTHENTICATED | CASH_WITHDRAWEL_SELECTED, | |
PIN_CHANGE_SELECTED, ...EXIT |
If we take a look at cash withdrawal the challenge is to efficiently handle cash withdrawals at an ATM by determining the denominations of banknotes to dispense as change. The goal is to make this process adaptable, maintainable, and flexible. The ATM must handle various denominations while selecting the optimal order of dispensing banknotes. Additionally, it should easily accommodate changes in denominations over time without altering the core code.
The Chain of Responsibility pattern is an ideal solution. It starts with the highest denomination and progresses to lower ones, efficiently fulfilling the withdrawal. Each handler is responsible for its denomination, and if it can't fully meet the withdrawal amount, it delegates the remainder to the next handler. This pattern allows for easy modification and adaptation as new denominations emerge, adhering to key software design principles and keeping the code modular and maintainable.
#include <iostream> #include <vector> using namespace std; class ATMWithdrawalHandler { public: ATMWithdrawalHandler* next{nullptr}; virtual int getDenomination() const = 0; virtual void handle(int& amount) = 0; void setNext(ATMWithdrawalHandler* handler) { if (next) next->setNext(handler); else next = handler; } void dispense(int& amount) { if (next) next->handle(amount); } }; class Denomination2000Handler : public ATMWithdrawalHandler { public: int getDenomination() const override { return 2000; } void handle(int& amount) override { int denomination = getDenomination(); if (amount >= denomination) { int numNotes = amount / denomination; amount %= denomination; cout << numNotes << " x " << denomination << " notes" << endl; } dispense(amount); } }; class Denomination500Handler : public ATMWithdrawalHandler { public: int getDenomination() const override { return 500; } void handle(int& amount) override { int denomination = getDenomination(); if (amount >= denomination) { int numNotes = amount / denomination; amount %= denomination; cout << numNotes << " x " << denomination << " notes" << endl; } dispense(amount); } }; class Denomination100Handler : public ATMWithdrawalHandler { public: int getDenomination() const override { return 100; } void handle(int& amount) override { int denomination = getDenomination(); if (amount >= denomination) { int numNotes = amount / denomination; amount %= denomination; cout << numNotes << " x " << denomination << " notes" << endl; } dispense(amount); } }; class Denomination10Handler : public ATMWithdrawalHandler { public: int getDenomination() const override { return 10; } void handle(int& amount) override { int denomination = getDenomination(); if (amount >= denomination) { int numNotes = amount / denomination; amount %= denomination; cout << numNotes << " x " << denomination << " notes" << endl; } dispense(amount); } }; int main() { Denomination2000Handler handler2000; Denomination500Handler handler500; Denomination100Handler handler100; Denomination10Handler handler10; handler2000.setNext(&handler500); handler500.setNext(&handler100); handler100.setNext(&handler10); int withdrawalAmount; cout<<"Enter Withdrawel Amount: "; cin>>withdrawalAmount; cout << "ATM Dispensing " << withdrawalAmount << " as:" << endl; handler2000.handle(withdrawalAmount); return 0; }
Design UML
ATM Manager creates an eagerly initialised, instance of a singleton ATM, it sets the denominations, their counts and available balance in the ATM and sets the ATM to the IDLE state
When the inserted card is validated and the pin authenticated,state of the ATM is modified, a User is created for the ATM instance, createUser, createUserCard and createUserBankAccount are called for it
Additional Notes
Since all our DenominationHandlers are remarkably similar we can create them via factory design patterns in combination with our chain of responsibilities:
#include <iostream> #include <vector> using namespace std; class ATMWithdrawalHandler { public: ATMWithdrawalHandler* next{ nullptr }; virtual int getDenomination() const = 0; virtual void handle(int& amount) = 0; virtual ~ATMWithdrawalHandler() {} // Make the destructor virtual to avoid issues with derived classes. void setNext(ATMWithdrawalHandler* handler) { if (next)next->setNext(handler); else next = handler; } void dispense(int& amount) { if (next) next->handle(amount); } }; class DenominationHandlerFactory { public: static ATMWithdrawalHandler* createHandler(int denom) { class DenominationHandler : public ATMWithdrawalHandler { public: DenominationHandler(int denomination) { this->denomination = denomination; } int getDenomination() const override { return denomination; } void handle(int& amount) override { if (amount >= denomination) { int numNotes = amount / denomination; amount %= denomination; cout << numNotes << " x " << denomination << " notes" << endl; } dispense(amount); } private: int denomination; }; return new DenominationHandler(denom); } }; int main() { int denominations[] = {2000, 500, 100, 10,1}; vector<ATMWithdrawalHandler*> handlers; for (int denom : denominations) { ATMWithdrawalHandler* handler = DenominationHandlerFactory::createHandler(denom); handlers.push_back(handler); } for (size_t i = 0; i < handlers.size() - 1; ++i) { handlers[i]->setNext(handlers[i + 1]); } int withdrawalAmount; cout << "Enter Withdrawal Amount: "; cin >> withdrawalAmount; cout << "ATM Dispensing " << withdrawalAmount << " as:" << endl; handlers[0]->handle(withdrawalAmount); // Clean up created handlers for (ATMWithdrawalHandler* handler : handlers) { delete handler; } return 0; }