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
andbalance
and methods likedeposit
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
andlogTransaction
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 toSavingsAccount
,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!