Java Code Example: Banking System with Encapsulation and Composition

This example models a Banking System where an Account class uses composition to interact with a TransactionLogger class.


Java Code Example: Banking System with Encapsulation and Composition

import java.util.ArrayList;
import java.util.List;

// Class to handle transaction logging (Composition)
class TransactionLogger {
    private List<String> transactionHistory = new ArrayList<>();

    // Method to log transactions
    public void logTransaction(String transaction) {
        transactionHistory.add(transaction);
    }

    // Method to display transaction history
    public void displayTransactions() {
        System.out.println("Transaction History:");
        for (String transaction : transactionHistory) {
            System.out.println(transaction);
        }
    }
}

// Class representing a Bank Account
class BankAccount {
    private String accountHolder;
    private double balance;
    private TransactionLogger logger; // Composition: Using another class

    // Constructor to initialize account
    public BankAccount(String accountHolder, double initialDeposit) {
        this.accountHolder = accountHolder;
        this.balance = initialDeposit;
        this.logger = new TransactionLogger(); // Creating logger instance
        logger.logTransaction("Account created with initial deposit: $" + initialDeposit);
    }

    // Method to deposit money
    public void deposit(double amount) {
        balance += amount;
        logger.logTransaction("Deposited: $" + amount);
    }

    // Method to withdraw money
    public boolean withdraw(double amount) {
        if (amount > balance) {
            logger.logTransaction("Failed withdrawal attempt: Insufficient funds.");
            return false;
        }
        balance -= amount;
        logger.logTransaction("Withdrawn: $" + amount);
        return true;
    }

    // Method to display account balance
    public void displayBalance() {
        System.out.println(accountHolder + "'s Balance: $" + balance);
    }

    // Method to show transaction history
    public void displayTransactionHistory() {
        logger.displayTransactions();
    }
}

// Main class
public class BankingSystem {
    public static void main(String[] args) {
        // Creating a bank account
        BankAccount account = new BankAccount("Alice Johnson", 1000.0);

        // Performing transactions
        account.deposit(500.0);
        account.withdraw(300.0);
        account.withdraw(1500.0); // Should fail due to insufficient funds

        // Displaying balance and transaction history
        account.displayBalance();
        account.displayTransactionHistory();
    }
}

Expected Output:

Alice Johnson's Balance: $1200.0
Transaction History:
Account created with initial deposit: $1000.0
Deposited: $500.0
Withdrawn: $300.0
Failed withdrawal attempt: Insufficient funds.

Code Explanation:

  1. Encapsulation:
    • The BankAccount class keeps balance private.
    • Transactions are recorded through a TransactionLogger instance, ensuring controlled access.
  2. Composition (Instead of Inheritance):
    • BankAccount contains an instance of TransactionLogger (has-a relationship).
    • Instead of extending another class, BankAccount delegates logging responsibility to TransactionLogger.
  3. Abstraction:
    • TransactionLogger abstracts logging logic, making it reusable for different classes (e.g., future SavingsAccount, LoanAccount).

Why This Approach?

Encapsulation – Prevents direct access to sensitive data.
Composition over Inheritance – More flexible and modular than rigid inheritance hierarchies.
Separation of Concerns – The banking logic and transaction history are managed independently, making the code cleaner and more maintainable.

This Java example demonstrates advanced object-oriented design with a real-world banking system, ensuring scalability and maintainability.