|

Banking System

Spread the love

Project Description

Design and implement a basic C++ banking application that models US-style accounts, transactions, and client relationships via Social Security Numbers (SSNs). The system must include:


1. Client Class

The Client class serves as the representation of an individual bank customer. Each client is uniquely identified by their personal information and may own multiple bank accounts. The class is designed to be shared among multiple accounts belonging to the same individual.

Attributes:

  • clientName – Stores the full name of the client.
  • SSN – A unique identifier (Social Security Number) used to associate clients with their bank accounts.

Purpose:
The Client object is referenced by all accounts belonging to the same person. This design allows centralized management of a client’s identity and easy retrieval of all related accounts.


2. BankAccount Class

The BankAccount class encapsulates the structure and functionality of an individual bank account. Each account is linked to a single client but a client may have multiple such accounts.

Attributes:

  • accountNumber (std::string) – A unique identifier for each account.
  • accountType – Describes the nature of the account, such as “Checking” or “Savings”.
  • balance (double) – Represents the current available balance in the account.
  • transactionHistory – A list of transaction records, each timestamped to ensure chronological tracking of activity.
  • owner (std::shared_ptr) – A smart pointer referencing the client who owns the account.

Methods:

  • deposit(amount) – Adds a specified amount to the current balance.
  • withdraw(amount) – Deducts a specified amount from the account, ensuring overdrafts are handled appropriately.
  • recordTransaction(details) – Logs each transaction with relevant information and a timestamp.
  • display() – Outputs account details, including balance and recent transactions, in a user-readable format.

3. BankManager Class

The BankManager class acts as the central controller responsible for overseeing the bank’s operations. It maintains all accounts and provides administrative functions to manage them.

Responsibilities:

  • accountExists(accountNumber) – Checks if an account with the given number exists in the system.
  • addAccount(std::shared_ptr, accountNumber, type, initialDeposit) – Creates a new account associated with a client and initializes it with a starting balance.
  • findAccount(accountNumber) – Retrieves a reference to the account object using its account number.
  • removeAccount(accountNumber) – Deletes the specified account from the system.
  • transfer(fromAccount, toAccount, amount) – Safely transfers funds between two accounts, ensuring both accounts are valid and the source has sufficient funds.
  • getAccountsBySSN(ssn) – Returns a list of all accounts that belong to the client with the specified SSN.
  • countAccountsBySSN(ssn) – Returns the total number of accounts linked to the provided SSN.
  • displayAllAccounts() – Displays detailed information about every account managed by the bank.

Behavioral Requirements

1. Transaction Logging

Every financial operation, including account creation, deposits, withdrawals, and transfers, must be recorded in the account’s transaction history. Each entry should include a descriptive message and a timestamp to indicate when the operation occurred. This ensures transparency and accountability in financial activity.

2. Input Validation and Error Handling

The system must robustly handle invalid or potentially harmful operations. This includes:

  • Preventing negative deposit or withdrawal amounts.
  • Gracefully rejecting withdrawals that would result in an overdraft.
  • Verifying that all account numbers referenced in operations exist.
  • Ensuring funds are not transferred to or from non-existent accounts.

Solution :

Client Class :
// Include necessary headers
#include <iostream>     // For input/output operations (e.g., std::cout)
#include <vector>       // For using the std::vector container
#include <memory>       // For using smart pointers like std::shared_ptr
#include <algorithm>    // For algorithms like sort, find, etc. (not used yet but included)
#include <string>       // For using std::string
#include <ctime>        // For time-related functions (not used yet but included)

// Define a structure to represent a Client
struct Client {
    // Shared pointers to strings representing the client's name and SSN (Social Security Number)
    // std::shared_ptr allows multiple Clients to share ownership of the same string object.
    std::shared_ptr<std::string> name;
    std::shared_ptr<std::string> ssn;

    // Constructor for Client
    // Takes two shared_ptr<string> parameters by const reference to avoid copying
    Client(const std::shared_ptr<std::string>& n,
           const std::shared_ptr<std::string>& s)
        : name(n), ssn(s)  // Member initializer list initializes 'name' and 'ssn'
    {}
};

BankAccount class :
// Define a class to represent a bank account
class BankAccount {
public:
    // Shared pointers to owner's name and SSN
    // Allows the same name/SSN to be shared across multiple accounts or linked clients
    std::shared_ptr<std::string> ownerName;
    std::shared_ptr<std::string> ownerSSN;

    // Unique pointer to the account number
    // Ensures exclusive ownership: each account has a unique number that cannot be shared
    std::unique_ptr<std::string> accountNumber;

    // Type of account (e.g., Checking, Savings)
    std::string accountType;

    // Current balance in the account
    double balance;

    // A history of transactions stored as strings
    std::vector<std::string> transactionHistory;

    // Constructor for BankAccount
    BankAccount(const std::shared_ptr<std::string>& name,
                const std::shared_ptr<std::string>& ssn,
                const std::string& accNum,
                const std::string& type = "Checking",    // Default account type
                double initialBalance = 0.0)              // Default initial balance
        : ownerName(name), ownerSSN(ssn),
          accountNumber(std::make_unique<std::string>(accNum)),  // Create a unique_ptr from accNum
          accountType(type), balance(initialBalance)
    {
        // Record the creation of the account as the first transaction
        recordTransaction("Account opened", initialBalance);
    }

    // Records a transaction with a timestamp, description, amount, and resulting balance
    void recordTransaction(const std::string& description, double amount) {
        time_t now = time(nullptr);  // Get current time
        char timestamp[64];
        // Format the time into a human-readable string
        strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", localtime(&now));
        
        // Append a detailed transaction string to the history
        transactionHistory.push_back(
            std::string(timestamp) + " | " + description +
            " | Amount: $" + std::to_string(amount) +
            " | Balance: $" + std::to_string(balance)
        );
    }

    // Deposits money into the account
    void deposit(double amount) {
        // Validate deposit amount
        if (amount <= 0) {
            std::cerr << "Invalid deposit amount\n";
            return;
        }

        // Update balance and record the transaction
        balance += amount;
        recordTransaction("Deposit", amount);

        // Print confirmation
        std::cout << "Deposited $" << amount << " | New balance: $" << balance << "\n";
    }

    // Withdraws money from the account
    bool withdraw(double amount) {
        // Validate withdrawal: must be positive and not exceed the balance
        if (amount <= 0 || amount > balance) {
            std::cerr << "Withdrawal failed\n";
            return false;
        }

        // Deduct amount from balance and record the transaction
        balance -= amount;
        recordTransaction("Withdrawal", -amount);

        // Print confirmation
        std::cout << "Withdrew $" << amount << " | Remaining: $" << balance << "\n";
        return true;
    }

    // Displays account information and last transaction
    void display() const {
        std::cout << "\nAccount #" << *accountNumber << " (" << accountType << ")\n"
                  << "Client: " << *ownerName << "\n"
                  << "SSN: " << *ownerSSN << "\n"
                  << "Balance: $" << balance << "\n";

        // Display last transaction if available
        if (!transactionHistory.empty()) {
            std::cout << "Last Transaction: " << transactionHistory.back() << "\n";
        }

        std::cout << "-------------------------\n";
    }
};
BankManager class :
class BankManager {
    // Container to store bank accounts using smart pointers
    // std::unique_ptr ensures each BankAccount is exclusively owned and automatically cleaned up
    std::vector<std::unique_ptr<BankAccount>> accounts;

public:
    // Checks if an account with a given account number already exists
    bool accountExists(const std::string& accNum) const {
        // Uses std::any_of to scan through all accounts and compare account numbers
        return std::any_of(accounts.begin(), accounts.end(),
            [&accNum](const auto& acc) {
                // Dereference accountNumber (unique_ptr<string>) to compare values
                return *acc->accountNumber == accNum;
            });
    }

    // Adds a new account for a given client if the account number is unique
    void addAccount(const std::shared_ptr<Client>& client,
                    const std::string& accNum,
                    const std::string& type = "Checking",   // Default account type
                    double initialDeposit = 0.0)            // Default initial deposit
    {
        // Prevent account number duplication
        if (accountExists(accNum)) {
            std::cerr << "Error: Account number already exists\n";
            return;
        }

        // Create and store a new BankAccount
        accounts.push_back(std::make_unique<BankAccount>(
            // Use client's name or fallback to empty string if null (safety check)
            client->name ? client->name : std::make_shared<std::string>(""),
            // Use client's SSN or fallback to empty string if null
            client->ssn ? client->ssn : std::make_shared<std::string>(""),
            accNum, type, initialDeposit
        ));

        std::cout << "Account created successfully\n";
    }

    // Finds an account by account number and returns a raw pointer to the account
    // Returns nullptr if not found
    BankAccount* findAccount(const std::string& accNum) {
        // Use std::find_if to locate the matching account
        auto it = std::find_if(accounts.begin(), accounts.end(),
            [&accNum](const auto& acc) {
                return *acc->accountNumber == accNum;
            });

        // Return a raw pointer to the found account or nullptr if not found
        return (it != accounts.end()) ? it->get() : nullptr;
    }

    // Removes an account with the specified account number
    bool removeAccount(const std::string& accNum) {
        // If account doesn't exist, return false immediately
        if (!accountExists(accNum)) return false;

        // Use std::remove_if to mark matching account for deletion,
        // then erase it from the vector
        accounts.erase(
            std::remove_if(accounts.begin(), accounts.end(),
                [&accNum](const auto& acc) {
                    return *acc->accountNumber == accNum;
                }),
            accounts.end()
        );

        return true; // Indicate successful deletion
    }

    // Transfers money from one account to another
    bool transfer(const std::string& fromAcc,
                  const std::string& toAcc,
                  double amount)
    {
        // Locate source and destination accounts
        BankAccount* source = findAccount(fromAcc);
        BankAccount* dest = findAccount(toAcc);

        // Both accounts must exist
        if (!source || !dest) return false;

        // Attempt withdrawal first; only deposit if withdrawal succeeds
        if (source->withdraw(amount)) {
            dest->deposit(amount);
            return true;
        }

        return false; // Transfer failed
    }

    // Retrieves all accounts owned by a person with a specific SSN
    std::vector<BankAccount*> getAccountsBySSN(const std::string& ssn) {
        std::vector<BankAccount*> result;

        // Use std::copy_if to collect pointers to matching accounts
        std::copy_if(accounts.begin(), accounts.end(), std::back_inserter(result),
            [&ssn](const auto& acc) {
                return *acc->ownerSSN == ssn;
            });

        return result;
    }

    // Counts how many accounts belong to a given SSN
    int countAccountsBySSN(const std::string& ssn) const {
        // Uses std::count_if to count how many times the SSN appears
        return std::count_if(accounts.begin(), accounts.end(),
            [&ssn](const auto& acc) {
                return *acc->ownerSSN == ssn;
            });
    }

    // Displays information for all existing accounts
    void displayAllAccounts() const {
        std::cout << "\n===== ALL ACCOUNTS =====\n";

        // Check if there are any accounts to display
        if (accounts.empty()) {
            std::cout << "No accounts available\n";
        } else {
            // Display each account using its own display method
            std::for_each(accounts.begin(), accounts.end(),
                [](const auto& acc) {
                    acc->display();
                });
        }

        std::cout << "========================\n";
    }
};

Explanation of the code :

Lambda Function

In C++, lambda functions are a concise way to define anonymous function objects directly in the scope where they are used. Introduced in C++11, a lambda function has the general syntax [capture](parameters) -> return_type { body }, where the capture clause allows access to variables from the surrounding scope, parameters are the function arguments, and the body contains the logic to be executed. Lambda functions are especially useful for short operations like sorting, filtering, or callbacks, where defining a full function would be unnecessary. For example, std::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; }); uses a lambda to specify the comparison logic inline, making code more readable and localized. Lambdas can also be assigned to variables and invoked like regular functions, supporting both mutable state and generic types in modern C++ standards.

Used Predefined Functions :

AlgorithmPurposeExample Capture
find_ifLocate the first element matching a condition[&accNum] to match account number
count_ifCount elements satisfying a predicate[&ssn] to count accounts by SSN
any_ofCheck if any element meets a condition[&accNum] to check for duplicates
copy_ifCopy elements passing a filter into another container[&ssn] to collect accounts by SSN
for_eachApply an operation to every element[](auto &acc) to call display()
transformMap elements to new values (e.g., extract balances)[&](auto &acc){ return acc->balance; }

Internally, the system uses smart pointers and STL algorithms to keep ownership clear and operations safe. For example, each account’s data is managed by a std::unique_ptr<BankAccount>, ensuring only the BankManager ever deletes it, while client information is shared via std::shared_ptr<Client>:

// Shared client pointer reused across accounts
auto clientPtr = std::make_shared<Client>("Alice", "111-22-3333");
bank.addAccount(clientPtr, "CHK1001", "Checking", 500);
bank.addAccount(clientPtr, "SAV2001", "Savings", 1000);

Looking up accounts returns a raw pointer, since ownership remains with the vector:

BankAccount* acc = bank.findAccount("CHK1001");
if (acc) {
    // Directly modify balance
    acc->deposit(150.0);
}

When transferring funds, the code chains methods while checking both pointers:

BankAccount* src = bank.findAccount("SAV2001");
BankAccount* dst = bank.findAccount("CHK1001");
if (src && dst && src->withdraw(300.0)) {
    dst->deposit(300.0);
}

Finally, iterating over all accounts uses for_each and lambdas to call display() without manual indexing:

std::for_each(accounts.begin(), accounts.end(),
    [](const auto& uptr) { uptr->display(); });

These patterns—smart pointers for memory, raw pointers for access, and STL algorithms for iteration—work together to keep the implementation concise, safe, and clear.

If you have any questions , feel free to contact us at [email protected]

Similar Posts