ATM
Table of Contents
Introduction
In this long article, we’ll build a simulated terminal ATM (Automated Teller Machine) in Java. But before we dive into the code, I will explain our folder structure and the architecture.
Overview
The ATM System follows a layered architecture comprising several distinct layers. The User Interface Layer manages user interactions through the Main
class, capturing input and displaying information via the command-line interface. The Controller Layer includes the ATM
class, which orchestrates system operations like cash reserve management and user transactions. The Model Layer contains data models and entities like the AtmCard
, CashReserve
, and Transaction
, representing what their name implies for the ATM card.
The Strategy Layer defines cash dispensing strategies through the CashDispenserStrategy
interface and implementations like SimpleCashDispenseStrategy
, which determines the cash dispensing logic. The Enums Package includes enumerations like Denomination
for currency types and PaymentNetwork
for payment networks. Lastly, the Models Package provides concrete implementations of ATM cards, such as VisaCard
and MasterCard
, extending the AtmCard
class with bank and network-specific details. Our folder structure is below:
- Controllers
- Enums
- Models
- Models > ATMCards
- Strategies
The ATM System will also support several features; like User Login, Cash Withdrawal with the system verifying the PIN and checking cash availability before dispensing the requested amount, PIN Change, Balance Inquiry letting users check the balance of their linked bank account, and Mini Statement which allows users to view recent transactions if any.
Each of the sections in this article will be the title for each folder accompanied with an explanation for what the file does under it.
Controllers
// ATM
;
;
Above, we define an ATM controller class. This class manages the core functionalities of the ATM. It creates a unique identifier for the ATM, stores the cash reserve information, and allows setting a strategy for dispensing cash. Additionally, it handles user transactions after successful card insertion and PIN verification. Users can withdraw cash, change PIN, check balance, get mini statements, and exit. Our class also provides functionalities for authorized admins to modify cash reserve and verify login, our admin login password being root
.
// Transaction
;
// Contains all facilities provided by ATM to users
Above, we define a class to handle various user transactions at the ATM. It relies on user input through a Scanner object. We then define the fuctionality, the user can;
withdraw an amount. We check the cash availability using the CashDispenserStrategy
and deduct the amount from the cash reserve. If successful, cash is dispensed, otherwise we show an error message. Change their pin by entering a new PIN, which is then sent to the bank (simulated) for verification and update. Check their balance by retrieving the balance for the account linked to the inserted ATM card (simulated). Print a mini statement. Verify their pin through a method that prompts the user for a PIN and sends it to the ATM card’s bank (simulated) for verification. It returns true if the PIN is correct.
Enums
// Denomination
The code for Denomination.java defines an enum called Denomination. Enums are a special type in Java that represent a fixed set of named constants. In this case, the Denomination enum represents the different denominations of currency that the ATM could dispense.
The enum consists of five constants: ONE, TWO, FIVE, TEN, TWENTY, FIFTY, and HUNDRED. The constraints are the face values of the bills the ATM can provide (e.g., 100, 50, 20, 10, 5, 2, 1 units of currency).
// PaymentNetwork
Similar to the Denomination enum, this file represents the different supported payment networks that the ATM interacts with.
Models
// Bank
Above, our Bank
class offers a constructor that takes the bank name as input and initializes the bankName
field. With an additional method called verifyAtmPin
. This method simulates the process of verifying the PIN entered by the user at the ATM. It takes the card number and the entered PIN as arguments. Since this is a simulated solution, the verifyAtmPin
function returns true no matter what.
// CashAvailabilityDataResponseModel
We have two member variables: isCashAvailable
: a boolean flag that says whether the requested amount can be dispensed based on the current cash reserve in the ATM. cashReserve
: an object of type CashReserve.
// CashReserve
;
Here, we define a class to manage the ATM’s cash reserve. The class uses a HashMap
to store the amount of each denomination available in the ATM. We initilize a HashMap
named cashReserveMap
with all possible Denomination enums as keys and sets their initial values to 0 (representing no bills of that denomination). The modifyCashReserve
method takes another CashReserve
object as input and merges its cash reserve information with the current ATM’s reserve. Then it iterates through the input cashReserveMap
and adds the count for each denomination to the existing count in the ATM’s cashReserveMap
. Further down, the getCashReserveMap
method returns a copy of the internal cashReserveMap
, providing a read-only view of the current cash reserve. The updateCash
method allows updating the count for a specific denomination. It takes a Denomination
enum and an integer count as arguments and directly updates the corresponding value in the cashReserveMap
. We’re also ensuring type safety and readability by using a HashMap
with Denomination
enums as keys.
// Currency
Up top, we’re using a static method to get the value associated with a given denomination. The getValue
method is static, meaning it can be called without creating an instance of the Currency
class. It takes a Denomination
enum as input and uses a switch statement to return the corresponding integer value. For example, if the input is Denomination.FIFTY
, it returns 50.
Models > AtmCard
// AtmCard
;
For the above, we define a class named AtmCard
that represents an ATM card. It stores information about the card including card number, CVV code, payment network (e.g., VISA, Mastercard), owner name, expiry date, and the associated bank. Then our class offers methods to set the payment network and bank after the card is created, as well as getter methods to access the card details.
// VisaCard
;
Above, we define a class named VisaCard
that inherits from the AtmCard
class. It specifically represents a Bank of America card. We inherit the card details (number, CVV, owner name, expiry) from the AtmCard
class and pre-configure details for Bank of America cards.
// MasterCard
;
Above, we define a class named MasterCard
that inherits from the AtmCard
class similar to VisaCard
. It specifically represents a American Express card.
Following the same logic as VisaCard
, this class simplifies creating American Express cards by: Inheriting card details from the AtmCard
class. Setting the paymentNetwork
to PaymentNetwork.AMEX
and bank
to a new Bank
object with the name “American Express” in the constructor. This pre-configures these details for this card.
Strategies
// CashDispenserStrategy
For our file above, we define an interface called CashDispenserStrategy
. This interface outlines the functionalities required for any strategy that dispenses cash from the ATM. For any class implementing this interface, they must use the dispenseCash
and getCashAvailability
methods. One to withdraw cash and the other to calculate the current currency amount.
// SimpleCashDispenseStrategy
;
// Works for our current denomination
And before our Main
java file to run the app, we have the class SimpleCashDispenseStrategy
that implements the CashDispenserStrategy
interface. This strategy focuses on dispensing cash using the available denominations in the ATM.
The dispenseCash method subtracts the requested cash amount (represented by another CashReserve
object) directly from the current ATM’s cash reserve. The getCashAvailability method checks if the ATM can dispense a requested withdrawal amount. It iterates through the available denominations and tries to build a combination of bills that fulfills the withdrawal amount.
It uses a loop to keep subtracting the denomination value from the requested amount as long as there are enough bills of that denomination and the remaining amount is greater than zero. If a sufficient combination is found (meaning the remaining amount becomes zero), it marks cash as available and stores the dispensed bill combination in a temporary CashReserve
object. Then in the end, it returns a response object indicating cash availability and the potential dispensed cash composition (if available).
Main
// Main
;
;
// Our password for admin is: "root"
For our entry point to the run the program, we define two main functionalities: user login and admin login. For user login, we simulate inserting a pre-defined ATM card and starting a transaction. Admin login requires a password, it’s . If successful, it allows modifying the cash reserve in the ATM. This modification involves specifying the bill denomination (e.g., two thousand rupees) and the number of bills to be added. The program has error handling to catch exceptions during cash reserve updates.
Conclusion
This project covered alot of the functionality of building an ATM in Java. We used built and instantiated interfaces using their methods to simulate an ATM. Hope you enjoyed the article and it helped you. Until then, have a good one!