In this blog post, we’ll explore how to build a simple banking system using object-oriented programming (OOP) in C++. We’ll cover various concepts like abstraction, inheritance, polymorphism, and dynamic memory management, all of which come together to simulate a real-world banking environment.
Overview of the Banking System
Our banking system comprises three core components:
- Accounts: This includes checking and savings accounts, each with unique behavior like overdraft limits and interest accumulation.
- Customers: Customers can have one or more accounts, and they can perform transactions on these accounts.
- Bank: The bank manages accounts and allows transactions like deposits and withdrawals.
The design is based on OOP principles, where each entity (like accounts or customers) is represented as a class with relevant properties and methods.
Class Structure
The banking system consists of four main classes:
- Account (Base Class): This is an abstract class that represents a generic account. It holds the basic properties of any account, like the account number, owner’s name, and balance. Since different types of accounts (like checking or savings) have their specific behavior, the Account class defines two pure virtual functions: deposit() and withdraw(), which are overridden by derived classes.
- CheckingAccount (Derived Class): Inherits from Account. It adds functionality specific to checking accounts, such as an overdraft limit.
- SavingsAccount (Derived Class): Inherits from Account. It adds a feature for accumulating interest, typical for savings accounts.
- Customer: Represents a bank customer who can own multiple accounts.
- Bank: Manages all accounts and handles transactions such as deposits and withdrawals.
Let’s dive into the code and its implementation.
1. Account Class (Abstract Base Class)
The Account class is an abstract base class, which means we cannot create an instance of it directly. The Account class defines some common properties that all account types will have, such as accountNumber, name, and balance. It also declares two pure virtual functions, deposit() and withdraw(), which will be implemented by the subclasses.
class Account {
protected:
int accountNumber;
string name;
double balance;
public:
Account(int accountNumber, string name, double balance = 0.0)
: accountNumber(accountNumber), name(name), balance(balance) {}
virtual void deposit(double amount) = 0;
virtual void withdraw(double amount) = 0;
double getBalance() const {
return balance;
}
int getAccountNumber() const {
return accountNumber;
}
};
2. CheckingAccount Class
The CheckingAccount class extends the Account class and implements the deposit() and withdraw() functions. A unique feature of the CheckingAccount is the overdraft limit. If a customer tries to withdraw more than their balance, the overdraft limit allows them to overdraw, but only up to a specified limit.
class CheckingAccount : public Account {
private:
double overdraftLimit;
public:
CheckingAccount(int accountNumber, string name, double balance = 0.0, double overdraftLimit = 1000.0)
: Account(accountNumber, name, balance), overdraftLimit(overdraftLimit) {}
void deposit(double amount) override {
balance += amount;
cout << "Deposited $" << amount << " into account " << accountNumber << ". New balance: $" << balance << endl;
}
void withdraw(double amount) override {
if (amount > balance + overdraftLimit) {
cout << "Insufficient funds!" << endl;
}
else {
balance -= amount;
cout << "Withdrew $" << amount << " from account " << accountNumber << ". New balance: $" << balance << endl;
}
}
};
3. SavingsAccount Class
The SavingsAccount class also extends the Account class and includes the functionality to accumulate interest. Savings accounts are typically designed to reward customers for keeping money in their account, so this class includes an interestRate and an addInterest() method to add interest to the account balance.
class SavingsAccount : public Account {
private:
double interestRate;
public:
SavingsAccount(int accountNumber, string name, double balance = 0.0, double interestRate = 0.05)
: Account(accountNumber, name, balance), interestRate(interestRate) {}
void deposit(double amount) override {
balance += amount;
cout << "Deposited $" << amount << " into account " << accountNumber << ". New balance: $" << balance << endl;
}
void withdraw(double amount) override {
if (amount > balance) {
cout << "Insufficient funds!" << endl;
}
else {
balance -= amount;
cout << "Withdrew $" << amount << " from account " << accountNumber << ". New balance: $" << balance << endl;
}
}
void addInterest() {
double interest = balance * interestRate;
balance += interest;
cout << "Added $" << interest << " interest to account " << accountNumber << ". New balance: $" << balance << endl;
}
};
4. Bank Class
The Bank class acts as a manager for all the accounts. It allows the creation of accounts and performs transactions like deposits and withdrawals. This class also handles memory management, deleting dynamically created account objects in its destructor to prevent memory leaks.
class Bank {
private:
vector<Account*> accounts;
public:
~Bank() {
for (Account* account : accounts) {
delete account;
}
}
void createAccount(int accountNumber, string name, string accountType, double balance = 0.0) {
if (accountType == "Checking") {
accounts.push_back(new CheckingAccount(accountNumber, name, balance));
}
else if (accountType == "Savings") {
accounts.push_back(new SavingsAccount(accountNumber, name, balance));
}
cout << "Account " << accountNumber << " created for " << name << " with initial balance $" << balance << endl;
}
Account* getAccount(int accountNumber) {
for (Account* account : accounts) {
if (account->getAccountNumber() == accountNumber) {
return account;
}
}
return nullptr;
}
void performTransaction(int accountNumber, string transactionType, double amount) {
Account* account = getAccount(accountNumber);
if (account) {
if (transactionType == "Deposit") {
account->deposit(amount);
}
else if (transactionType == "Withdrawal") {
account->withdraw(amount);
}
}
else {
cout << "Account not found!" << endl;
}
}
};
5. Customer Class
The Customer class represents a customer who can hold multiple accounts. Each customer can add accounts and display their account details, such as the account number and balance.
class Customer {
private:
string name;
string address;
string phoneNumber;
vector<Account*> accounts;
public:
Customer(string name, string address, string phoneNumber)
: name(name), address(address), phoneNumber(phoneNumber) {}
void addAccount(Account* account) {
if (account != nullptr) {
accounts.push_back(account);
}
}
void displayAccounts() const {
cout << "Accounts for " << name << ":" << endl;
for (Account* account : accounts) {
cout << "Account Number: " << account->getAccountNumber() << ", Balance: $" << account->getBalance() << endl;
}
}
};
Running the Program: main() Function
In the main() function, we create a bank and two customers, John and Jane. Each customer gets one account (John gets a checking account, Jane gets a savings account). We then perform various transactions, display account information, and calculate interest for Jane’s savings account.
int main() {
Bank bank;
bank.createAccount(1234, "John Doe", "Checking", 1000.0);
bank.createAccount(5678, "Jane Doe", "Savings", 500.0);
Customer customer1("John Doe", "123 Main St", "123-456-7890");
customer1.addAccount(bank.getAccount(1234));
Customer customer2("Jane Doe", "456 Elm St", "987-654-3210");
customer2.addAccount(bank.getAccount(5678));
bank.performTransaction(1234, "Deposit", 500.0);
bank.performTransaction(1234, "Withdrawal", 200.0);
bank.performTransaction(5678, "Deposit", 300.0);
bank.performTransaction(5678, "Withdrawal", 100.0);
customer1.displayAccounts();
customer2.displayAccounts();
// Add interest to savings account
SavingsAccount* savingsAccount = dynamic_cast<SavingsAccount*>(bank.getAccount(5678));
if (savingsAccount) {
savingsAccount->addInterest();
}
customer2.displayAccounts();
return 0;
}
Summary
This C++ program demonstrates how object-oriented principles can be applied to model a simple banking system. With inheritance, we can create specialized account types, and with polymorphism, we can manage accounts in a uniform way, while each behaves according to its specific type.
This structure not only makes the code cleaner and more modular but also mirrors the way real-world banking systems are organized. The use of dynamic memory allocation and proper memory management in the Bank class ensures that the program efficiently manages resources and prevents memory leaks.
Feel free to expand on this example by adding more functionality, such as account transfers, loan accounts, or more complex interest calculations!
Discover more from lounge coder
Subscribe to get the latest posts sent to your email.