Friday, January 10, 2025

 Unlocking the Power of Inheritance in Java: Extending the Encapsulated BankAccount

What is Inheritance?

Inheritance allows a class (called the child or subclass) to acquire the properties and behaviors of another class (called the parent or superclass). It helps developers:

  • Reuse existing code, reducing redundancy.

  • Extend functionality to meet new requirements.

  • Maintain cleaner and more manageable codebases.

In this post, we’ll use the encapsulated BankAccount class from a previous discussion on encapsulation to showcase how inheritance works. Step by step, we’ll explore the advantages of inheritance and implement them using the BankAccount class.


Advantage 1: Code Reusability

One of the most significant benefits of inheritance is code reusability. Instead of rewriting common properties and methods for every new type of account, we can define them once in the parent class and reuse them in subclasses.

Implementation: Reusing BankAccount in SavingsAccount

Let’s create a SavingsAccount class to represent an account with an interest rate. We don’t need to rewrite fields like accountNumber, balance, or methods like deposit and withdraw. These can be inherited from the BankAccount class:

public class SavingsAccount extends BankAccount {
    private double interestRate;

    public SavingsAccount(String accountNumber, String password, 
                          double initialBalance, double interestRate) {
        super(accountNumber, password, initialBalance);
        this.interestRate = interestRate;
    }

    public double calculateInterest(String enteredPassword) {
        double balance = getBalance(enteredPassword);
        if (balance != -1) {
            return balance * (interestRate / 100);
        } else {
            return 0;
        }
    }

    public void applyInterest(String enteredPassword) {
        double interest = calculateInterest(enteredPassword);
        if (interest > 0) {
            deposit(interest);
            System.out.println("Interest applied: " + interest);
        }
    }
}

Benefit in Action:

  • Fields like accountNumber and balance and methods like deposit are directly reused from the parent class.

  • The SavingsAccount only implements additional behavior (interest-related logic), making the code cleaner and more focused.


Advantage 2: Extensibility

Inheritance enables us to extend the behavior of existing classes to handle new requirements. This is particularly useful when adding specialized functionalities for subclasses.

Note: For better encapsulation, we’ve made the authenticate and logTransaction methods package-private, ensuring they are accessible within the same package while limiting external access.

Implementation: Extending Behavior in CheckingAccount

Let’s implement a CheckingAccount class, which includes an overdraft feature. We’ll override the withdraw method to allow withdrawals beyond the account balance, up to a defined overdraft limit:

public class CheckingAccount extends BankAccount {
    private double overdraftLimit;

    public CheckingAccount(String accountNumber, String password, 
                          double initialBalance, double overdraftLimit) {
        super(accountNumber, password, initialBalance);
        this.overdraftLimit = overdraftLimit;
    }

    @Override
    public void withdraw(double amount, String enteredPassword) {
        if (authenticate(enteredPassword)) {
            if (amount > 0 && getBalance(enteredPassword) + overdraftLimit >= amount) {
                double currentBalance = getBalance(enteredPassword);
                if (currentBalance >= amount) {
                    super.withdraw(amount, enteredPassword);
                } else {
                    double overdraftUsed = amount - currentBalance;
                    logTransaction("Overdraft", overdraftUsed);
                    System.out.println("Overdraft of " + overdraftUsed + " used.");
                    System.out.println("Transaction completed.");
                }
            } else {
                System.out.println("Invalid withdrawal amount or exceeds overdraft limit!");
            }
        } else {
            System.out.println("Invalid password! Transaction denied.");
        }
    }
}

Benefit in Action:

  • The CheckingAccount class reuses the encapsulated withdrawal logic from the parent class while adding an overdraft-specific check.

  • Overriding the withdraw method demonstrates how inheritance allows behavior customization.


Advantage 3: Maintainability

Inheritance ensures centralized management of common functionality. Any change in the parent class automatically reflects in all subclasses, reducing maintenance overhead.

Implementation: Updating the Base Class

The BankAccount class already includes features like password encryption and transaction logging. Any improvement, such as enhancing the logTransaction method, will immediately apply to all subclasses:

void logTransaction(String type, double amount) {
    System.out.println(type + " of $" + amount + " logged on " + 
                        java.time.LocalDateTime.now());
}

Benefit in Action:

  • Adding logging in the BankAccount class applies to SavingsAccount, CheckingAccount, and any other subclass automatically.

  • This eliminates duplication and ensures consistent behavior across all account types.


Advantage 4: Scalability

Inheritance supports scalability by making it easy to add new features or account types. For example, if we need a FixedDepositAccount, we can quickly create it by extending BankAccount.

Implementation: Adding FixedDepositAccount

A FixedDepositAccount could include a lock-in period and penalties for early withdrawal:

public class FixedDepositAccount extends BankAccount {
    private int lockInPeriodInMonths;

    public FixedDepositAccount(String accountNumber, String password, 
                          double initialBalance, int lockInPeriodInMonths) {
        super(accountNumber, password, initialBalance);
        this.lockInPeriodInMonths = lockInPeriodInMonths;
    }

    public int getLockInPeriod() {
        return lockInPeriodInMonths;
    }

    @Override
    public void withdraw(double amount, String enteredPassword) {
        System.out.println("Withdrawals are not allowed before the lock-in period.");
    }
}

Benefit in Action:

  • By inheriting from BankAccount, FixedDepositAccount gains basic functionality like deposit, while enforcing its own withdrawal rules.

  • This demonstrates how inheritance supports new features without disrupting existing code.


Conclusion

Inheritance is a powerful tool in Java that builds on encapsulation to enhance code reusability, extensibility, maintainability, and scalability. By using the BankAccount class as a foundation, we’ve demonstrated how to leverage inheritance to create specialized account types efficiently.

In the next post, we’ll explore polymorphism, focusing on how subclasses can be used interchangeably with their parent class to create flexible and dynamic programs. This will keep us aligned with our goal of discussing the key pillars of OOP in Java. Additionally, we’ll cover the various types of inheritance in a separate post to ensure a comprehensive understanding of these concepts. Stay tuned for more practical insights into Java programming!

GitHub repo