What is Encapsulation in Object-Oriented Programming
Encapsulation is one of the fundamental pillars of object-oriented programming (OOP) and represents the principle of hiding implementation details of a class, exposing only what is necessary through a well-defined public interface. This concept is crucial for creating maintainable, secure, and modular software.
🎯 Goals of Encapsulation
- Information Hiding: Hide implementation details
- Data Protection: Prevent unauthorized access and modifications
- Flexibility: Change implementation without impacting external code
- Maintainability: Reduce coupling between components
Access Modifiers in TypeScript
TypeScript offers three access modifiers to implement encapsulation:
private, protected, and public. Choosing the right modifier
determines who can access class members.
1. Private: Internal Access Only
private members are accessible only within the class that defines them.
This is the most restrictive level of encapsulation and should be the default for all
internal class data.
class BankAccount {{ '{' }}
// Private properties - internal access only
private balance: number;
private accountNumber: string;
private transactionHistory: Transaction[] = [];
private isLocked: boolean = false;
constructor(accountNumber: string, initialBalance: number) {{ '{' }}
this.accountNumber = accountNumber;
this.balance = initialBalance;
this.logTransaction('Account created', initialBalance);
{{ '}' }}
// Public method with validation
public deposit(amount: number): void {{ '{' }}
if (this.isLocked) {{ '{' }}
throw new Error('Account is locked');
{{ '}' }}
if (amount <= 0) {{ '{' }}
throw new Error('Amount must be positive');
{{ '}' }}
this.balance += amount;
this.logTransaction('Deposit', amount);
{{ '}' }}
public withdraw(amount: number): boolean {{ '{' }}
if (this.isLocked) {{ '{' }}
throw new Error('Account is locked');
{{ '}' }}
if (amount > this.balance) {{ '{' }}
console.log('Insufficient funds');
return false;
{{ '}' }}
if (amount <= 0) {{ '{' }}
throw new Error('Amount must be positive');
{{ '}' }}
this.balance -= amount;
this.logTransaction('Withdrawal', amount);
return true;
{{ '}' }}
// Public getter - read-only access
public getBalance(): number {{ '{' }}
return this.balance;
{{ '}' }}
public getAccountNumber(): string {{ '{' }}
return this.accountNumber;
{{ '}' }}
// Private method - internal helper
private logTransaction(type: string, amount: number): void {{ '{' }}
const transaction: Transaction = {{ '{' }}
id: Date.now().toString(),
type,
amount,
timestamp: new Date(),
balanceAfter: this.balance
{{ '}' }};
this.transactionHistory.push(transaction);
{{ '}' }}
// Method to get copy of history (not the original array!)
public getTransactionHistory(): readonly Transaction[] {{ '{' }}
return Object.freeze([...this.transactionHistory]);
{{ '}' }}
// Administrative methods
public lockAccount(): void {{ '{' }}
this.isLocked = true;
{{ '}' }}
public unlockAccount(adminCode: string): void {{ '{' }}
if (this.verifyAdminCode(adminCode)) {{ '{' }}
this.isLocked = false;
{{ '}' }}
{{ '}' }}
private verifyAdminCode(code: string): boolean {{ '{' }}
// Secure verification logic
return code === 'ADMIN_SECRET_CODE';
{{ '}' }}
{{ '}' }}
interface Transaction {{ '{' }}
id: string;
type: string;
amount: number;
timestamp: Date;
balanceAfter: number;
{{ '}' }}
// Usage
const account = new BankAccount('IT60X0542811101000000123456', 1000);
account.deposit(500); // ✅ OK
console.log(account.getBalance()); // ✅ OK - 1500
// account.balance = 999999; // ❌ ERROR: balance is private
// account.logTransaction(); // ❌ ERROR: logTransaction is private
✨ Advantages of Private Members
- Total Control: Only the class can modify internal state
- Centralized Validation: All modifications go through validated public methods
- Safe Refactoring: Can change private implementation without breaking external code
- Easy Debugging: Know exactly where state is modified
2. Protected: Access for Subclasses
protected members are accessible from the class and its subclasses.
Useful when you want to allow extension but maintain encapsulation from the outside.
abstract class Vehicle {{ '{' }}
protected engineStatus: 'on' | 'off' = 'off';
protected currentSpeed: number = 0;
protected readonly maxSpeed: number;
constructor(maxSpeed: number) {{ '{' }}
this.maxSpeed = maxSpeed;
{{ '}' }}
// Protected method - usable by subclasses
protected startEngine(): void {{ '{' }}
if (this.engineStatus === 'off') {{ '{' }}
this.engineStatus = 'on';
console.log('Engine started');
{{ '}' }}
{{ '}' }}
protected stopEngine(): void {{ '{' }}
if (this.currentSpeed === 0) {{ '{' }}
this.engineStatus = 'off';
console.log('Engine stopped');
{{ '}' }} else {{ '{' }}
console.log('Cannot stop engine while moving');
{{ '}' }}
{{ '}' }}
// Public method
public getSpeed(): number {{ '{' }}
return this.currentSpeed;
{{ '}' }}
// Abstract method - must be implemented
abstract accelerate(amount: number): void;
{{ '}' }}
class Car extends Vehicle {{ '{' }}
private gear: number = 1;
constructor() {{ '{' }}
super(200); // maxSpeed for car
{{ '}' }}
accelerate(amount: number): void {{ '{' }}
// Can access protected members of parent
if (this.engineStatus === 'off') {{ '{' }}
this.startEngine(); // ✅ OK - protected method
{{ '}' }}
if (this.currentSpeed + amount <= this.maxSpeed) {{ '{' }}
this.currentSpeed += amount;
this.adjustGear();
{{ '}' }}
{{ '}' }}
brake(amount: number): void {{ '{' }}
this.currentSpeed = Math.max(0, this.currentSpeed - amount);
if (this.currentSpeed === 0) {{ '{' }}
this.stopEngine(); // ✅ OK - protected method
{{ '}' }}
{{ '}' }}
private adjustGear(): void {{ '{' }}
// Private gear shifting logic
if (this.currentSpeed > 80) this.gear = 5;
else if (this.currentSpeed > 60) this.gear = 4;
else if (this.currentSpeed > 40) this.gear = 3;
else if (this.currentSpeed > 20) this.gear = 2;
else this.gear = 1;
{{ '}' }}
{{ '}' }}
// Usage
const car = new Car();
car.accelerate(50);
console.log(car.getSpeed()); // 50
// car.startEngine(); // ❌ ERROR: startEngine is protected
// car.engineStatus; // ❌ ERROR: engineStatus is protected
3. Public: Free Access
public members are accessible from anywhere in the code. These
constitute the class's public interface and should be
minimized and well-documented.
⚠️ Principle of Least Privilege
Always start with private and only relax when necessary. It's easier to make something public later than to make private something that was public (breaking change).
- Private by default
- Protected only if necessary for subclasses
- Public only for essential interface
Getters and Setters: Controlled Access
Getters and setters in TypeScript offer an elegant way to control property access, allowing validation and custom logic.
class User {{ '{' }}
private _email: string;
private _age: number;
private _passwordHash: string;
constructor(email: string, age: number, password: string) {{ '{' }}
this._email = email;
this._age = age;
this._passwordHash = this.hashPassword(password);
{{ '}' }}
// Getter - read-only access with transformation
get email(): string {{ '{' }}
return this._email.toLowerCase();
{{ '}' }}
// Setter with validation
set email(value: string) {{ '{' }}
if (!this.isValidEmail(value)) {{ '{' }}
throw new Error('Invalid email format');
{{ '}' }}
this._email = value;
{{ '}' }}
// Getter
get age(): number {{ '{' }}
return this._age;
{{ '}' }}
// Setter with validation
set age(value: number) {{ '{' }}
if (value < 0 || value > 150) {{ '{' }}
throw new Error('Invalid age');
{{ '}' }}
this._age = value;
{{ '}' }}
// Computed property - getter only
get isAdult(): boolean {{ '{' }}
return this._age >= 18;
{{ '}' }}
get ageGroup(): string {{ '{' }}
if (this._age < 18) return 'minor';
if (this._age < 65) return 'adult';
return 'senior';
{{ '}' }}
// Public method to change password
changePassword(oldPassword: string, newPassword: string): boolean {{ '{' }}
if (!this.verifyPassword(oldPassword)) {{ '{' }}
return false;
{{ '}' }}
if (newPassword.length < 8) {{ '{' }}
throw new Error('Password must be at least 8 characters');
{{ '}' }}
this._passwordHash = this.hashPassword(newPassword);
return true;
{{ '}' }}
// Private methods - helpers
private isValidEmail(email: string): boolean {{ '{' }}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
{{ '}' }}
private hashPassword(password: string): string {{ '{' }}
// Hash simulation (use bcrypt in production!)
return `hash_






