Encapsulation
Encapsulation refers to bundling data (variables) and methods that operate on the data into a single unit or class, and restricting access to some of the object's components.
Purpose: It helps protect the integrity of the data and prevents unintended interference or misuse.
Problem
Without proper encapsulation, where the balance and accountHolder are publicly accessible and modifiable
ts
class BankAccount {
// Public properties: no restrictions on accessing or modifying them
public balance: number;
public accountHolder: string;
constructor(accountHolder: string, initialBalance: number) {
this.accountHolder = accountHolder;
this.balance = initialBalance;
}
// Public method: deposit money into the account
public deposit(amount: number): void {
if (amount <= 0) {
console.log('Deposit amount must be greater than zero.');
} else {
this.balance += amount;
console.log(`Deposited $${amount}. New balance is $${this.balance}.`);
}
}
// Public method: withdraw money from the account
public withdraw(amount: number): void {
if (amount <= 0) {
console.log('Withdrawal amount must be greater than zero.');
} else if (amount > this.balance) {
console.log('Insufficient balance.');
} else {
this.balance -= amount;
console.log(`Withdrew $${amount}. New balance is $${this.balance}.`);
}
}
}
const account = new BankAccount("John Doe", 1000);
account.balance = 5000; // No restriction: balance can be changed directly
account.accountHolder = "Jane Smith"; // No restriction: accountHolder can be changed directly
console.log(`Balance: $${account.balance}`);
Solution
Private Property (balance):
- The balance is marked as private, meaning it cannot be accessed or modified directly outside the BankAccount class. This ensures that no external code can directly change the balance, which helps prevent accidental or malicious modifications.
Protected Property (accountHolder):
- The accountHolder property is marked as protected, which means it can be accessed within the class and by subclasses. In this example, the PremiumBankAccount subclass can access accountHolder and use it, but external code cannot.
Public Methods (deposit, withdraw, getBalance):
- The
deposit
andwithdraw
methods are public, allowing other code to interact with theBankAccount
object in a controlled way. They include validation to ensure that deposits and withdrawals are valid. - The
getBalance
method is public and provides a read-only way to access the balance.
- The
Protected Method (displayAccountInfo):
- The
displayAccountInfo
method is marked asprotected
, meaning it is only accessible within the class and by any subclasses. In this example, thePremiumBankAccount
class can call the protected method fromBankAccount
to display account information.
- The
Encapsulation with Inheritance:
- The subclass
PremiumBankAccount
can access theaccountHolder
anddisplayAccountInfo
due to the protected modifier, but external code cannot modify these properties directly. This demonstrates how encapsulation and inheritance can work together to provide controlled access to data.
- The subclass
ts
class BankAccount {
// Private property: balance should not be directly modified from outside
private balance: number;
// Protected property: `accountHolder` can be accessed by subclasses
protected accountHolder: string;
// Constructor to initialize the account details
constructor(accountHolder: string, initialBalance: number) {
this.accountHolder = accountHolder;
this.balance = initialBalance;
}
// Public method: deposit money into the account
public deposit(amount: number): void {
if (amount <= 0) {
console.log('Deposit amount must be greater than zero.');
} else {
this.balance += amount;
console.log(`Deposited $${amount}. New balance is $${this.balance}.`);
}
}
// Public method: withdraw money from the account
public withdraw(amount: number): void {
if (amount <= 0) {
console.log('Withdrawal amount must be greater than zero.');
} else if (amount > this.balance) {
console.log('Insufficient balance.');
} else {
this.balance -= amount;
console.log(`Withdrew $${amount}. New balance is $${this.balance}.`);
}
}
// Getter for the balance (read-only access to balance)
public getBalance(): number {
return this.balance;
}
// Protected method: used internally in the class or subclasses to display account info
protected displayAccountInfo(): void {
console.log(`Account Holder: ${this.accountHolder}, Balance: $${this.balance}`);
}
}
// Subclass to demonstrate usage of protected property
class PremiumBankAccount extends BankAccount {
private membershipStatus: string;
constructor(accountHolder: string, initialBalance: number, membershipStatus: string) {
super(accountHolder, initialBalance);
this.membershipStatus = membershipStatus;
}
// Public method: Display account info, which includes the protected property
public displayPremiumAccountInfo(): void {
this.displayAccountInfo(); // Accessing protected method of the parent class
console.log(`Membership Status: ${this.membershipStatus}`);
}
}
// Create an instance of BankAccount
const basicAccount = new BankAccount("John Doe", 1000);
basicAccount.deposit(500); // Depositing money into the account
basicAccount.withdraw(200); // Withdrawing money from the account
console.log(`Balance: $${basicAccount.getBalance()}`); // Access balance via getter
// Cannot directly access balance or accountHolder from outside the class
// console.log(basicAccount.balance); // Error: 'balance' is private and only accessible within class 'BankAccount'
// console.log(basicAccount.accountHolder); // Error: 'accountHolder' is protected and accessible only within the class and subclasses
// Create an instance of PremiumBankAccount (a subclass)
const premiumAccount = new PremiumBankAccount("Jane Smith", 5000, "Gold");
premiumAccount.displayPremiumAccountInfo(); // Accessing inherited protected method from the parent class