+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 29 of 355

πŸ”’ Access Modifiers: public, private, protected, and readonly

Master TypeScript access modifiers to create secure, maintainable classes with proper encapsulation and inheritance patterns πŸš€

πŸš€Intermediate
28 min read

Prerequisites

  • Understanding of TypeScript classes and constructors πŸ—οΈ
  • Basic knowledge of object-oriented programming πŸ“š
  • Familiarity with inheritance concepts πŸ‘₯

What you'll learn

  • Master all four access modifiers and their use cases 🎯
  • Implement proper encapsulation in TypeScript classes πŸ”’
  • Design secure inheritance hierarchies πŸ›‘οΈ
  • Create maintainable and robust class interfaces ✨

🎯 Introduction

Welcome to the fortress of TypeScript access modifiers! 🏰 Think of access modifiers as security guards for your class members - they control who can access what, when, and how.

Just like a house has different levels of access (public lobby, private bedroom, family room for relatives), your classes need different levels of member visibility. Access modifiers are your tools for creating secure, well-designed code that follows the principle of β€œleast privilege” πŸ”.

By the end of this tutorial, you’ll be the master of encapsulation, crafting classes that are both powerful and secure! Let’s unlock the secrets of access control! πŸ—οΈ

πŸ“š Understanding Access Modifiers

πŸ€” What are Access Modifiers?

Access modifiers control the visibility and accessibility of class members:

  • public 🌍: Accessible from anywhere
  • private πŸ”’: Only accessible within the same class
  • protected πŸ‘₯: Accessible within the class and subclasses
  • readonly πŸ“–: Can be assigned once, then becomes immutable

Think of them like security clearance levels:

  • 🌍 Public: Anyone can enter (lobby)
  • πŸ‘₯ Protected: Family members only (family room)
  • πŸ”’ Private: Owner only (private office)
  • πŸ“– Readonly: Look but don’t touch (museum display)

πŸ’‘ Why Use Access Modifiers?

Here’s why access modifiers are essential:

  1. Encapsulation πŸ”’: Hide internal implementation details
  2. Security πŸ›‘οΈ: Prevent unauthorized access to sensitive data
  3. Maintainability πŸ› οΈ: Control how your class can be used
  4. API Design 🎯: Create clean, intuitive interfaces
  5. Inheritance Control πŸ‘₯: Manage what subclasses can access

🌍 Public Access Modifier

βœ… Public - The Open Door

public members are accessible from anywhere - inside the class, outside the class, and in subclasses.

// πŸ‘€ User class with public members
class User {
  // 🌍 Public properties - accessible everywhere
  public name: string;
  public email: string;
  public isActive: boolean;
  
  constructor(name: string, email: string) {
    this.name = name;
    this.email = email;
    this.isActive = true;
    console.log(`πŸ‘‹ Welcome ${name}!`);
  }
  
  // 🌍 Public methods - can be called from anywhere
  public login(): void {
    if (this.isActive) {
      console.log(`πŸ”‘ ${this.name} logged in successfully!`);
    } else {
      console.log(`❌ Account for ${this.name} is inactive`);
    }
  }
  
  public logout(): void {
    console.log(`πŸ‘‹ ${this.name} logged out`);
  }
  
  // 🌍 Public getter/setter
  public getProfile(): string {
    return `πŸ‘€ ${this.name} (${this.email}) - ${this.isActive ? 'βœ… Active' : '❌ Inactive'}`;
  }
  
  public updateEmail(newEmail: string): void {
    // πŸ›‘οΈ Even public methods can have validation
    if (newEmail.includes('@')) {
      this.email = newEmail;
      console.log(`πŸ“§ Email updated to: ${newEmail}`);
    } else {
      console.log(`❌ Invalid email format: ${newEmail}`);
    }
  }
}

// πŸš€ Public members are accessible everywhere
const user = new User("Alice", "[email protected]");

// βœ… Direct access to public properties
console.log(user.name);          // "Alice"
console.log(user.email);         // "[email protected]"
user.isActive = false;           // Direct modification allowed

// βœ… Direct access to public methods
user.login();                    // Works from outside
user.updateEmail("[email protected]");
console.log(user.getProfile());

// πŸ’‘ Note: Default visibility in TypeScript is 'public'
class SimpleClass {
  // These are implicitly public
  property: string = "I'm public!";
  
  method(): void {
    console.log("I'm also public!");
  }
}

πŸ”’ Private Access Modifier

πŸ›‘οΈ Private - Fort Knox Security

private members are only accessible within the same class - not from outside or even from subclasses.

// 🏦 BankAccount class with private members
class BankAccount {
  // 🌍 Public interface
  public readonly accountNumber: string;
  public readonly accountHolder: string;
  
  // πŸ”’ Private properties - internal use only
  private balance: number;
  private pin: string;
  private transactionHistory: string[];
  private isLocked: boolean;
  
  constructor(accountHolder: string, initialDeposit: number, pin: string) {
    this.accountNumber = this.generateAccountNumber();
    this.accountHolder = accountHolder;
    this.balance = initialDeposit;
    this.pin = pin;
    this.transactionHistory = [`Initial deposit: $${initialDeposit}`];
    this.isLocked = false;
    
    console.log(`🏦 Account ${this.accountNumber} created for ${accountHolder}`);
  }
  
  // πŸ”’ Private method - only used internally
  private generateAccountNumber(): string {
    return `ACC${Date.now()}${Math.floor(Math.random() * 1000)}`;
  }
  
  // πŸ”’ Private validation method
  private validatePin(inputPin: string): boolean {
    return this.pin === inputPin;
  }
  
  // πŸ”’ Private method to add transaction
  private addTransaction(description: string): void {
    const timestamp = new Date().toLocaleString();
    this.transactionHistory.push(`${timestamp}: ${description}`);
  }
  
  // 🌍 Public methods that use private members safely
  public checkBalance(pin: string): number | null {
    if (this.isLocked) {
      console.log("πŸ”’ Account is locked. Contact support.");
      return null;
    }
    
    if (!this.validatePin(pin)) {
      console.log("❌ Invalid PIN");
      return null;
    }
    
    console.log(`πŸ’° Balance: $${this.balance.toFixed(2)}`);
    return this.balance;
  }
  
  public deposit(amount: number, pin: string): boolean {
    if (this.isLocked || !this.validatePin(pin)) {
      console.log("❌ Transaction denied");
      return false;
    }
    
    if (amount <= 0) {
      console.log("❌ Deposit amount must be positive");
      return false;
    }
    
    this.balance += amount;
    this.addTransaction(`Deposit: +$${amount.toFixed(2)}`);
    console.log(`βœ… Deposited $${amount.toFixed(2)}. New balance: $${this.balance.toFixed(2)}`);
    return true;
  }
  
  public withdraw(amount: number, pin: string): boolean {
    if (this.isLocked || !this.validatePin(pin)) {
      console.log("❌ Transaction denied");
      return false;
    }
    
    if (amount <= 0) {
      console.log("❌ Withdrawal amount must be positive");
      return false;
    }
    
    if (amount > this.balance) {
      console.log("❌ Insufficient funds");
      return false;
    }
    
    this.balance -= amount;
    this.addTransaction(`Withdrawal: -$${amount.toFixed(2)}`);
    console.log(`βœ… Withdrew $${amount.toFixed(2)}. Remaining balance: $${this.balance.toFixed(2)}`);
    return true;
  }
  
  // πŸ”’ Private method for internal use
  private lockAccount(): void {
    this.isLocked = true;
    this.addTransaction("Account locked due to security concerns");
    console.log("πŸ”’ Account has been locked for security");
  }
  
  // 🌍 Public method to get transaction history (with PIN)
  public getTransactionHistory(pin: string): string[] | null {
    if (!this.validatePin(pin)) {
      console.log("❌ Invalid PIN");
      return null;
    }
    
    return [...this.transactionHistory]; // Return copy to prevent modification
  }
}

// πŸš€ Test private access control
const account = new BankAccount("John Doe", 1000, "1234");

// βœ… These work - public interface
console.log(account.accountNumber);
console.log(account.accountHolder);
account.checkBalance("1234");
account.deposit(500, "1234");

// ❌ These would cause TypeScript errors:
// console.log(account.balance);        // Error: 'balance' is private
// account.pin = "5678";                // Error: 'pin' is private
// account.validatePin("1234");         // Error: 'validatePin' is private
// account.lockAccount();               // Error: 'lockAccount' is private

console.log("πŸ’³ Account Details:");
console.log(`Account: ${account.accountNumber}`);
console.log(`Holder: ${account.accountHolder}`);

πŸ‘₯ Protected Access Modifier

🏠 Protected - Family Only

protected members are accessible within the class and its subclasses, but not from outside.

// 🐾 Animal base class with protected members
class Animal {
  // 🌍 Public interface
  public name: string;
  public species: string;
  
  // πŸ‘₯ Protected - available to subclasses
  protected age: number;
  protected energy: number;
  protected habitat: string;
  
  // πŸ”’ Private - only for this class
  private id: string;
  
  constructor(name: string, species: string, age: number, habitat: string) {
    this.name = name;
    this.species = species;
    this.age = age;
    this.energy = 100;
    this.habitat = habitat;
    this.id = `${species}_${Date.now()}`;
    
    console.log(`🐾 ${species} ${name} has been born!`);
  }
  
  // πŸ‘₯ Protected methods - subclasses can use these
  protected consumeEnergy(amount: number): void {
    this.energy = Math.max(0, this.energy - amount);
    console.log(`⚑ ${this.name} consumed ${amount} energy. Remaining: ${this.energy}`);
  }
  
  protected restoreEnergy(amount: number): void {
    this.energy = Math.min(100, this.energy + amount);
    console.log(`😴 ${this.name} restored ${amount} energy. Current: ${this.energy}`);
  }
  
  protected checkHabitat(requiredHabitat: string): boolean {
    return this.habitat === requiredHabitat;
  }
  
  // πŸ”’ Private method - only this class can use
  private generateId(): string {
    return `${this.species}_${Date.now()}`;
  }
  
  // 🌍 Public methods
  public getInfo(): string {
    return `🐾 ${this.name} the ${this.species} (${this.age} years old) - Energy: ${this.energy}%`;
  }
  
  public sleep(): void {
    console.log(`😴 ${this.name} is sleeping...`);
    this.restoreEnergy(30);
  }
}

// πŸ• Dog class extending Animal
class Dog extends Animal {
  private tricks: string[];
  
  constructor(name: string, age: number, breed: string) {
    super(name, "Dog", age, "Domestic"); // Call parent constructor
    this.tricks = [];
    console.log(`πŸ• ${breed} dog ${name} is ready to play!`);
  }
  
  // πŸ• Dog-specific method using protected members
  public bark(): void {
    // βœ… Can access protected members from parent
    if (this.energy < 10) {
      console.log(`😴 ${this.name} is too tired to bark`);
      return;
    }
    
    console.log(`πŸ• ${this.name} says: Woof! Woof!`);
    this.consumeEnergy(5); // βœ… Using protected method
  }
  
  public play(): void {
    // βœ… Can check protected habitat
    if (!this.checkHabitat("Domestic")) {
      console.log(`🏠 ${this.name} needs to be in a domestic environment to play safely`);
      return;
    }
    
    console.log(`🎾 ${this.name} is playing fetch!`);
    this.consumeEnergy(15); // βœ… Using protected method
  }
  
  public learnTrick(trick: string): void {
    if (this.energy < 20) {
      console.log(`😴 ${this.name} is too tired to learn new tricks`);
      return;
    }
    
    this.tricks.push(trick);
    this.consumeEnergy(10);
    console.log(`πŸŽͺ ${this.name} learned to ${trick}!`);
  }
  
  // 🎯 Override parent method with additional functionality
  public getInfo(): string {
    const baseInfo = super.getInfo(); // βœ… Call parent method
    const tricksInfo = this.tricks.length > 0 ? ` | Tricks: ${this.tricks.join(', ')}` : '';
    return `${baseInfo}${tricksInfo}`;
  }
  
  // πŸ“Š Method that accesses protected properties
  public getDetailedStatus(): string {
    // βœ… Can access protected properties
    return `
πŸ• ${this.name} Status Report:
πŸ“Š Age: ${this.age} years
⚑ Energy: ${this.energy}%
🏠 Habitat: ${this.habitat}
πŸŽͺ Tricks Known: ${this.tricks.length}
    `.trim();
  }
}

// 🐱 Cat class extending Animal
class Cat extends Animal {
  private lives: number = 9;
  
  constructor(name: string, age: number, isIndoor: boolean) {
    const habitat = isIndoor ? "Indoor" : "Outdoor";
    super(name, "Cat", age, habitat);
    console.log(`🐱 ${name} has ${this.lives} lives remaining!`);
  }
  
  public meow(): void {
    if (this.energy < 5) {
      console.log(`😴 ${this.name} is too tired to meow`);
      return;
    }
    
    console.log(`🐱 ${this.name} says: Meow!`);
    this.consumeEnergy(3); // βœ… Using protected method
  }
  
  public hunt(): void {
    // βœ… Check protected habitat
    if (this.checkHabitat("Indoor")) {
      console.log(`🏠 ${this.name} is an indoor cat and can't hunt outside`);
      return;
    }
    
    if (this.energy < 25) {
      console.log(`😴 ${this.name} is too tired to hunt`);
      return;
    }
    
    console.log(`🎯 ${this.name} is hunting prey!`);
    this.consumeEnergy(20);
    
    // Sometimes hunting is dangerous!
    if (Math.random() < 0.1) {
      this.loseLife();
    }
  }
  
  private loseLife(): void {
    if (this.lives > 1) {
      this.lives--;
      console.log(`πŸ’” ${this.name} lost a life! ${this.lives} lives remaining`);
    } else {
      console.log(`😿 ${this.name} has used all their lives`);
    }
  }
  
  // πŸ“Š Cat-specific status
  public getCatStatus(): string {
    // βœ… Access protected properties
    const status = this.checkHabitat("Indoor") ? "🏠 Indoor" : "🌳 Outdoor";
    return `🐱 ${this.name}: ${this.age} years, ${this.energy}% energy, ${this.lives} lives, ${status}`;
  }
}

// πŸš€ Test protected access
const buddy = new Dog("Buddy", 3, "Golden Retriever");
const whiskers = new Cat("Whiskers", 2, false);

console.log("\nπŸ“Š Animal Information:");
console.log(buddy.getInfo());
console.log(whiskers.getInfo());

console.log("\nπŸŽͺ Animal Actions:");
buddy.bark();
buddy.play();
buddy.learnTrick("sit");

whiskers.meow();
whiskers.hunt();

console.log("\nπŸ“‹ Detailed Status:");
console.log(buddy.getDetailedStatus());
console.log(whiskers.getCatStatus());

// βœ… These work - public methods
buddy.sleep();
whiskers.sleep();

// ❌ These would cause TypeScript errors:
// console.log(buddy.age);           // Error: 'age' is protected
// buddy.consumeEnergy(10);          // Error: 'consumeEnergy' is protected
// console.log(whiskers.energy);     // Error: 'energy' is protected

πŸ“– Readonly Access Modifier

πŸ”’ Readonly - Look But Don’t Touch

readonly properties can only be assigned during declaration or in the constructor, then become immutable.

// πŸ“œ Document class with readonly properties
class Document {
  // πŸ“– Readonly properties - can only be set once
  public readonly id: string;
  public readonly createdAt: Date;
  public readonly author: string;
  public readonly type: "pdf" | "docx" | "txt";
  
  // 🌍 Mutable properties
  public title: string;
  public content: string;
  public isPublished: boolean;
  
  // πŸ“– Readonly array - reference is readonly, but contents can change
  public readonly tags: string[];
  
  // πŸ“– Readonly object - reference is readonly
  public readonly metadata: {
    version: number;
    lastModified: Date;
    size: number;
  };
  
  constructor(
    author: string,
    title: string,
    type: "pdf" | "docx" | "txt",
    content: string = ""
  ) {
    // βœ… Can assign readonly properties in constructor
    this.id = `doc_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    this.createdAt = new Date();
    this.author = author;
    this.type = type;
    
    // Regular property assignment
    this.title = title;
    this.content = content;
    this.isPublished = false;
    
    // βœ… Initialize readonly array and object
    this.tags = [];
    this.metadata = {
      version: 1,
      lastModified: new Date(),
      size: content.length
    };
    
    console.log(`πŸ“„ Document "${title}" created by ${author}`);
  }
  
  // πŸ“ Update content (updates metadata)
  public updateContent(newContent: string): void {
    this.content = newContent;
    
    // ❌ Can't reassign readonly object, but can modify its properties
    // this.metadata = { ... };  // Error!
    
    // βœ… Can modify properties of readonly object
    this.metadata.version++;
    this.metadata.lastModified = new Date();
    this.metadata.size = newContent.length;
    
    console.log(`πŸ“ Document updated. Version: ${this.metadata.version}`);
  }
  
  // 🏷️ Add tag
  public addTag(tag: string): void {
    // ❌ Can't reassign readonly array
    // this.tags = [...this.tags, tag];  // Error!
    
    // βœ… Can modify contents of readonly array
    if (!this.tags.includes(tag)) {
      this.tags.push(tag);
      console.log(`🏷️ Added tag: ${tag}`);
    }
  }
  
  // πŸ—‘οΈ Remove tag
  public removeTag(tag: string): void {
    const index = this.tags.indexOf(tag);
    if (index !== -1) {
      this.tags.splice(index, 1);
      console.log(`πŸ—‘οΈ Removed tag: ${tag}`);
    }
  }
  
  // πŸ“Š Document info
  public getDocumentInfo(): string {
    const publishStatus = this.isPublished ? "πŸ“’ Published" : "πŸ“ Draft";
    const tagsStr = this.tags.length > 0 ? this.tags.join(", ") : "No tags";
    
    return `
πŸ“„ ${this.title} (${this.type.toUpperCase()})
πŸ‘€ Author: ${this.author}
πŸ†” ID: ${this.id}
πŸ“… Created: ${this.createdAt.toLocaleDateString()}
πŸ“Š Status: ${publishStatus}
πŸ“ Version: ${this.metadata.version}
πŸ“ Size: ${this.metadata.size} characters
🏷️ Tags: ${tagsStr}
    `.trim();
  }
  
  // πŸ“’ Publish document
  public publish(): void {
    if (this.content.trim().length === 0) {
      console.log("❌ Cannot publish empty document");
      return;
    }
    
    this.isPublished = true;
    this.metadata.lastModified = new Date();
    console.log(`πŸ“’ Document "${this.title}" has been published!`);
  }
}

// 🎨 Design Document with readonly configuration
class DesignDocument extends Document {
  // πŸ“– Additional readonly properties in subclass
  public readonly designTool: string;
  public readonly resolution: { width: number; height: number };
  
  constructor(
    author: string,
    title: string,
    designTool: string,
    resolution: { width: number; height: number }
  ) {
    super(author, title, "pdf");
    
    // βœ… Can set readonly properties in subclass constructor
    this.designTool = designTool;
    this.resolution = resolution; // Object reference is readonly
    
    console.log(`🎨 Design document created with ${designTool}`);
  }
  
  // πŸ–ΌοΈ Update resolution (modify readonly object properties)
  public updateResolution(width: number, height: number): void {
    // ❌ Can't reassign readonly object
    // this.resolution = { width, height };  // Error!
    
    // βœ… Can modify properties of readonly object
    this.resolution.width = width;
    this.resolution.height = height;
    
    this.metadata.lastModified = new Date();
    console.log(`πŸ–ΌοΈ Resolution updated to ${width}x${height}`);
  }
  
  // πŸ“Š Override to include design info
  public getDocumentInfo(): string {
    const baseInfo = super.getDocumentInfo();
    const designInfo = `\n🎨 Design Tool: ${this.designTool}\nπŸ–ΌοΈ Resolution: ${this.resolution.width}x${this.resolution.height}`;
    return baseInfo + designInfo;
  }
}

// πŸš€ Test readonly behavior
const report = new Document(
  "Alice Johnson",
  "TypeScript Best Practices",
  "docx",
  "This document covers TypeScript best practices..."
);

const logo = new DesignDocument(
  "Bob Designer",
  "Company Logo",
  "Adobe Illustrator",
  { width: 1920, height: 1080 }
);

console.log("\nπŸ“„ Document Operations:");

// βœ… These work - mutable properties
report.title = "Advanced TypeScript Best Practices";
report.updateContent("Updated content with more examples...");
report.addTag("typescript");
report.addTag("programming");
report.publish();

// βœ… Can modify properties of readonly objects
logo.updateResolution(2560, 1440);

console.log("\nπŸ“Š Document Information:");
console.log(report.getDocumentInfo());
console.log("\n" + logo.getDocumentInfo());

// ❌ These would cause TypeScript errors:
// report.id = "new_id";                    // Error: 'id' is readonly
// report.createdAt = new Date();           // Error: 'createdAt' is readonly
// report.author = "Different Author";      // Error: 'author' is readonly
// report.tags = ["new", "tags"];           // Error: 'tags' is readonly
// logo.designTool = "Photoshop";          // Error: 'designTool' is readonly

🎯 Access Modifier Patterns

πŸ—οΈ Builder Pattern with Access Control

// 🏠 House builder with controlled access
class House {
  // πŸ“– Readonly house specifications
  public readonly id: string;
  public readonly address: string;
  
  // πŸ”’ Private construction details
  private rooms: string[];
  private squareFootage: number;
  private isCompleted: boolean;
  
  // πŸ‘₯ Protected for subclasses
  protected buildingMaterials: string[];
  protected constructionCost: number;
  
  private constructor(
    address: string,
    squareFootage: number
  ) {
    this.id = `HOUSE_${Date.now()}`;
    this.address = address;
    this.squareFootage = squareFootage;
    this.rooms = [];
    this.isCompleted = false;
    this.buildingMaterials = [];
    this.constructionCost = 0;
  }
  
  // 🏭 Static factory method for controlled creation
  public static startConstruction(address: string, squareFootage: number): HouseBuilder {
    return new HouseBuilder(new House(address, squareFootage));
  }
  
  // πŸ”’ Private method to add room
  private addRoom(room: string): void {
    if (this.isCompleted) {
      throw new Error("❌ Cannot add rooms to completed house");
    }
    this.rooms.push(room);
  }
  
  // πŸ‘₯ Protected method for subclasses
  protected calculateBaseCost(): number {
    return this.squareFootage * 100;
  }
  
  // 🌍 Public getters
  public getAddress(): string {
    return this.address;
  }
  
  public getRooms(): readonly string[] {
    return [...this.rooms];
  }
  
  public getSquareFootage(): number {
    return this.squareFootage;
  }
  
  public isReady(): boolean {
    return this.isCompleted;
  }
  
  // πŸ“Š Public method to get house info
  public getHouseInfo(): string {
    const status = this.isCompleted ? "βœ… Completed" : "🚧 Under Construction";
    return `
🏠 House ${this.id}
πŸ“ Address: ${this.address}
πŸ“ Square Footage: ${this.squareFootage} sq ft
🏠 Rooms: ${this.rooms.join(", ") || "None yet"}
πŸ“Š Status: ${status}
πŸ’° Total Cost: $${this.constructionCost.toLocaleString()}
    `.trim();
  }
}

// πŸ”¨ Builder class with controlled access to House
class HouseBuilder {
  private house: House;
  
  // πŸ”’ Private constructor - only House can create
  constructor(house: House) {
    this.house = house;
  }
  
  // 🏠 Add rooms through builder
  public addBedroom(): this {
    (this.house as any).addRoom("Bedroom"); // Type assertion to access private
    (this.house as any).constructionCost += 15000;
    console.log("πŸ›οΈ Added bedroom");
    return this;
  }
  
  public addBathroom(): this {
    (this.house as any).addRoom("Bathroom");
    (this.house as any).constructionCost += 10000;
    console.log("🚿 Added bathroom");
    return this;
  }
  
  public addKitchen(): this {
    (this.house as any).addRoom("Kitchen");
    (this.house as any).constructionCost += 25000;
    console.log("🍳 Added kitchen");
    return this;
  }
  
  public addLivingRoom(): this {
    (this.house as any).addRoom("Living Room");
    (this.house as any).constructionCost += 20000;
    console.log("πŸ›‹οΈ Added living room");
    return this;
  }
  
  // πŸ—οΈ Complete construction
  public completeConstruction(): House {
    if ((this.house as any).rooms.length === 0) {
      throw new Error("❌ House must have at least one room");
    }
    
    (this.house as any).isCompleted = true;
    console.log("πŸŽ‰ House construction completed!");
    return this.house;
  }
}

// 🏑 Luxury House with additional features
class LuxuryHouse extends House {
  // πŸ”’ Private luxury features
  private luxuryFeatures: string[];
  
  constructor(address: string, squareFootage: number) {
    // ❌ Can't call private constructor directly
    // super(address, squareFootage);  // Error!
    
    // Must use static factory method from parent
    const tempHouse = House.startConstruction(address, squareFootage).completeConstruction();
    
    // Copy properties (in real implementation, you'd handle this differently)
    Object.assign(this, tempHouse);
    
    this.luxuryFeatures = [];
    console.log("✨ Luxury house features initialized");
  }
  
  // 🎯 Luxury-specific methods
  public addLuxuryFeature(feature: string, cost: number): void {
    this.luxuryFeatures.push(feature);
    this.constructionCost += cost; // βœ… Can access protected property
    console.log(`✨ Added luxury feature: ${feature} (+$${cost.toLocaleString()})`);
  }
  
  // πŸ“Š Override with luxury details
  public getHouseInfo(): string {
    const baseInfo = super.getHouseInfo();
    const luxuryInfo = this.luxuryFeatures.length > 0 
      ? `\n✨ Luxury Features: ${this.luxuryFeatures.join(", ")}`
      : "";
    return baseInfo + luxuryInfo;
  }
}

// πŸš€ Test access control in building pattern
console.log("πŸ—οΈ Building a house...\n");

const myHouse = House.startConstruction("123 Main St", 2000)
  .addBedroom()
  .addBedroom()
  .addBathroom()
  .addKitchen()
  .addLivingRoom()
  .completeConstruction();

console.log("\n" + myHouse.getHouseInfo());

// ❌ These would cause errors:
// const directHouse = new House("456 Oak St", 1500);  // Error: constructor is private
// myHouse.addRoom("Garage");                          // Error: addRoom is private
// console.log(myHouse.rooms);                         // Error: rooms is private

⚠️ Common Pitfalls and Solutions

😱 Pitfall 1: Overusing private

// ❌ Too restrictive - hard to extend or test
class BadBankAccount {
  private balance: number = 0;
  private transactions: string[] = [];
  
  private validateAmount(amount: number): boolean {
    return amount > 0;
  }
  
  private addTransaction(description: string): void {
    this.transactions.push(description);
  }
  
  // No way for subclasses to access or extend functionality
}

// βœ… Better balance of access control
class GoodBankAccount {
  private balance: number = 0;
  protected transactions: string[] = [];  // Subclasses can access
  
  protected validateAmount(amount: number): boolean {  // Subclasses can override
    return amount > 0;
  }
  
  protected addTransaction(description: string): void {  // Subclasses can use
    this.transactions.push(description);
  }
  
  // Clear public interface
  public deposit(amount: number): boolean {
    if (this.validateAmount(amount)) {
      this.balance += amount;
      this.addTransaction(`Deposit: +$${amount}`);
      return true;
    }
    return false;
  }
}

🀯 Pitfall 2: Readonly confusion

// ❌ Misunderstanding readonly behavior
class BadExample {
  public readonly items: string[] = [];
  public readonly config: { mode: string } = { mode: "development" };
  
  updateItems(): void {
    // ❌ Think this won't work, but it does!
    this.items.push("new item");     // βœ… Array contents can change
    this.config.mode = "production"; // βœ… Object properties can change
    
    // These are the operations that would fail:
    // this.items = [];              // ❌ Can't reassign array reference
    // this.config = { mode: "prod" }; // ❌ Can't reassign object reference
  }
}

// βœ… Proper readonly usage
class GoodExample {
  public readonly items: ReadonlyArray<string> = [];  // Truly immutable array
  public readonly config: Readonly<{ mode: string }> = { mode: "development" };
  
  updateItems(newItem: string): void {
    // βœ… Create new array instead of mutating
    (this as any).items = [...this.items, newItem];
  }
}

πŸ› οΈ Best Practices

  1. 🎯 Start Private: Begin with private, then expose as needed
  2. πŸ‘₯ Use Protected Wisely: For inheritance-friendly APIs
  3. 🌍 Minimal Public Interface: Only expose what users need
  4. πŸ“– Readonly for Immutability: Use for data that shouldn’t change
  5. πŸ”’ Encapsulation First: Hide implementation details
  6. πŸ“Š Consistent Patterns: Use same access patterns throughout

πŸ§ͺ Hands-On Exercise

🎯 Challenge: Build a Secure Task Management System

Create a comprehensive task management system with proper access control:

πŸ“‹ Requirements:

  • πŸ“ Task class with appropriate access modifiers
  • πŸ‘€ User class with protected user data
  • 🏒 TaskManager class with private task storage
  • πŸ” Permission system with different access levels
  • πŸ“Š Audit trail with readonly history
  • πŸ‘₯ Team management with inheritance

πŸš€ Bonus Features:

  • Role-based access control
  • Task encryption for sensitive tasks
  • Performance metrics with readonly data
  • Notification system with protected methods

πŸ’‘ Solution

πŸ” Click to see solution
// πŸ“ Task class with comprehensive access control
class Task {
  // πŸ“– Readonly properties that never change
  public readonly id: string;
  public readonly createdAt: Date;
  public readonly createdBy: string;
  
  // 🌍 Public properties for general access
  public title: string;
  public description: string;
  public priority: "low" | "medium" | "high";
  public status: "pending" | "in-progress" | "completed" | "cancelled";
  
  // πŸ‘₯ Protected properties for subclasses and managers
  protected assignedTo: string | null;
  protected dueDate: Date | null;
  protected category: string;
  
  // πŸ”’ Private properties for internal use only
  private isEncrypted: boolean;
  private encryptedData: string | null;
  private lastModified: Date;
  private modificationHistory: string[];
  
  constructor(
    title: string,
    description: string,
    createdBy: string,
    priority: "low" | "medium" | "high" = "medium"
  ) {
    this.id = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    this.createdAt = new Date();
    this.createdBy = createdBy;
    this.title = title;
    this.description = description;
    this.priority = priority;
    this.status = "pending";
    
    this.assignedTo = null;
    this.dueDate = null;
    this.category = "general";
    
    this.isEncrypted = false;
    this.encryptedData = null;
    this.lastModified = new Date();
    this.modificationHistory = [`Created by ${createdBy}`];
    
    console.log(`πŸ“ Task "${title}" created with ID: ${this.id}`);
  }
  
  // πŸ”’ Private method for adding to history
  private addToHistory(action: string, user: string): void {
    const timestamp = new Date().toISOString();
    this.modificationHistory.push(`${timestamp}: ${action} by ${user}`);
    this.lastModified = new Date();
  }
  
  // πŸ”’ Private encryption methods
  private encrypt(data: string): string {
    // Simple encryption for demo (use proper encryption in production)
    return Buffer.from(data).toString('base64');
  }
  
  private decrypt(encryptedData: string): string {
    return Buffer.from(encryptedData, 'base64').toString();
  }
  
  // πŸ‘₯ Protected method for managers and subclasses
  protected validatePermission(user: string, action: string): boolean {
    // Basic permission check - can be overridden by subclasses
    return user === this.createdBy || user === this.assignedTo;
  }
  
  // 🌍 Public method to assign task
  public assignTo(user: string, assignedBy: string): boolean {
    if (!this.validatePermission(assignedBy, "assign")) {
      console.log(`❌ ${assignedBy} doesn't have permission to assign this task`);
      return false;
    }
    
    this.assignedTo = user;
    this.addToHistory(`Assigned to ${user}`, assignedBy);
    console.log(`πŸ‘€ Task "${this.title}" assigned to ${user}`);
    return true;
  }
  
  // 🌍 Public method to update status
  public updateStatus(newStatus: typeof this.status, user: string): boolean {
    if (!this.validatePermission(user, "update")) {
      console.log(`❌ ${user} doesn't have permission to update this task`);
      return false;
    }
    
    const oldStatus = this.status;
    this.status = newStatus;
    this.addToHistory(`Status changed from ${oldStatus} to ${newStatus}`, user);
    console.log(`πŸ“Š Task "${this.title}" status updated to ${newStatus}`);
    return true;
  }
  
  // 🌍 Public method to set due date
  public setDueDate(dueDate: Date, user: string): boolean {
    if (!this.validatePermission(user, "update")) {
      console.log(`❌ ${user} doesn't have permission to set due date`);
      return false;
    }
    
    this.dueDate = dueDate;
    this.addToHistory(`Due date set to ${dueDate.toDateString()}`, user);
    console.log(`πŸ“… Due date set for "${this.title}": ${dueDate.toDateString()}`);
    return true;
  }
  
  // πŸ” Public method to encrypt sensitive tasks
  public encryptTask(user: string): boolean {
    if (user !== this.createdBy) {
      console.log(`❌ Only task creator can encrypt tasks`);
      return false;
    }
    
    if (this.isEncrypted) {
      console.log(`πŸ” Task is already encrypted`);
      return false;
    }
    
    this.encryptedData = this.encrypt(JSON.stringify({
      title: this.title,
      description: this.description
    }));
    
    this.title = "[ENCRYPTED]";
    this.description = "[ENCRYPTED]";
    this.isEncrypted = true;
    
    this.addToHistory("Task encrypted", user);
    console.log(`πŸ” Task encrypted by ${user}`);
    return true;
  }
  
  // πŸ”“ Public method to decrypt tasks
  public decryptTask(user: string): boolean {
    if (user !== this.createdBy) {
      console.log(`❌ Only task creator can decrypt tasks`);
      return false;
    }
    
    if (!this.isEncrypted || !this.encryptedData) {
      console.log(`πŸ”“ Task is not encrypted`);
      return false;
    }
    
    const decryptedData = JSON.parse(this.decrypt(this.encryptedData));
    this.title = decryptedData.title;
    this.description = decryptedData.description;
    this.isEncrypted = false;
    this.encryptedData = null;
    
    this.addToHistory("Task decrypted", user);
    console.log(`πŸ”“ Task decrypted by ${user}`);
    return true;
  }
  
  // 🌍 Public getters
  public getAssignedTo(): string | null {
    return this.assignedTo;
  }
  
  public getDueDate(): Date | null {
    return this.dueDate;
  }
  
  public getCategory(): string {
    return this.category;
  }
  
  public isOverdue(): boolean {
    if (!this.dueDate) return false;
    return new Date() > this.dueDate && this.status !== "completed";
  }
  
  // 🌍 Public method to get task info
  public getTaskInfo(requestingUser: string): string {
    const isAuthorized = this.validatePermission(requestingUser, "view");
    
    if (this.isEncrypted && !isAuthorized) {
      return `πŸ” Task ${this.id} - [ENCRYPTED] - Access Denied`;
    }
    
    const dueInfo = this.dueDate ? `πŸ“… Due: ${this.dueDate.toDateString()}` : "πŸ“… No due date";
    const assigneeInfo = this.assignedTo ? `πŸ‘€ Assigned: ${this.assignedTo}` : "πŸ‘€ Unassigned";
    const overdueFlag = this.isOverdue() ? " ⚠️ OVERDUE" : "";
    
    return `
πŸ“ ${this.title}${overdueFlag}
πŸ“‹ ${this.description}
πŸ†” ID: ${this.id}
πŸ“Š Status: ${this.status} | Priority: ${this.priority}
${assigneeInfo}
${dueInfo}
πŸ‘€ Created by: ${this.createdBy} on ${this.createdAt.toDateString()}
🏷️ Category: ${this.category}
    `.trim();
  }
  
  // 🌍 Public method to get history (limited access)
  public getHistory(requestingUser: string): string[] | null {
    if (!this.validatePermission(requestingUser, "view")) {
      console.log(`❌ ${requestingUser} doesn't have permission to view task history`);
      return null;
    }
    
    return [...this.modificationHistory]; // Return copy
  }
}

// 🎯 Priority Task with additional security
class PriorityTask extends Task {
  // πŸ”’ Private priority-specific properties
  private stakeholders: string[];
  private approvalRequired: boolean;
  private approvedBy: string | null;
  
  constructor(
    title: string,
    description: string,
    createdBy: string,
    stakeholders: string[] = []
  ) {
    super(title, description, createdBy, "high");
    this.stakeholders = stakeholders;
    this.approvalRequired = true;
    this.approvedBy = null;
    this.category = "priority";
    
    console.log(`⭐ Priority task created with ${stakeholders.length} stakeholders`);
  }
  
  // πŸ‘₯ Override permission validation for priority tasks
  protected validatePermission(user: string, action: string): boolean {
    // Priority tasks have stricter permissions
    const isCreator = user === this.createdBy;
    const isAssignee = user === this.assignedTo;
    const isStakeholder = this.stakeholders.includes(user);
    
    switch (action) {
      case "assign":
      case "approve":
        return isCreator || isStakeholder;
      case "update":
        return (isCreator || isAssignee) && (this.approvedBy !== null || !this.approvalRequired);
      case "view":
        return isCreator || isAssignee || isStakeholder;
      default:
        return super.validatePermission(user, action);
    }
  }
  
  // 🌍 Public method to approve priority task
  public approve(user: string): boolean {
    if (!this.validatePermission(user, "approve")) {
      console.log(`❌ ${user} doesn't have permission to approve this priority task`);
      return false;
    }
    
    this.approvedBy = user;
    this.approvalRequired = false;
    console.log(`βœ… Priority task "${this.title}" approved by ${user}`);
    return true;
  }
  
  // 🌍 Get priority task info with additional details
  public getTaskInfo(requestingUser: string): string {
    const baseInfo = super.getTaskInfo(requestingUser);
    
    if (!this.validatePermission(requestingUser, "view")) {
      return baseInfo;
    }
    
    const approvalStatus = this.approvedBy 
      ? `βœ… Approved by ${this.approvedBy}` 
      : "⏳ Pending approval";
    
    const stakeholderList = this.stakeholders.length > 0 
      ? `πŸ‘₯ Stakeholders: ${this.stakeholders.join(", ")}`
      : "πŸ‘₯ No stakeholders";
    
    return `${baseInfo}\n⭐ PRIORITY TASK\n${approvalStatus}\n${stakeholderList}`;
  }
}

// πŸ‘€ User class with protected data
class User {
  // πŸ“– Readonly user identification
  public readonly id: string;
  public readonly username: string;
  public readonly email: string;
  
  // πŸ‘₯ Protected user data for subclasses
  protected role: "user" | "manager" | "admin";
  protected permissions: string[];
  protected lastLogin: Date | null;
  
  // πŸ”’ Private sensitive data
  private hashedPassword: string;
  private loginAttempts: number;
  private isLocked: boolean;
  
  constructor(username: string, email: string, password: string) {
    this.id = `user_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
    this.username = username;
    this.email = email;
    this.role = "user";
    this.permissions = ["view_own_tasks", "create_tasks"];
    this.lastLogin = null;
    
    this.hashedPassword = this.hashPassword(password);
    this.loginAttempts = 0;
    this.isLocked = false;
    
    console.log(`πŸ‘€ User ${username} created with ID: ${this.id}`);
  }
  
  // πŸ”’ Private password hashing
  private hashPassword(password: string): string {
    // Simple hash for demo (use proper hashing in production)
    return Buffer.from(password).toString('base64');
  }
  
  // πŸ‘₯ Protected method for authentication
  protected authenticate(password: string): boolean {
    if (this.isLocked) {
      console.log(`πŸ”’ Account ${this.username} is locked`);
      return false;
    }
    
    const hashedInput = this.hashPassword(password);
    if (hashedInput === this.hashedPassword) {
      this.loginAttempts = 0;
      this.lastLogin = new Date();
      return true;
    }
    
    this.loginAttempts++;
    if (this.loginAttempts >= 3) {
      this.isLocked = true;
      console.log(`πŸ”’ Account ${this.username} locked due to too many failed attempts`);
    }
    
    return false;
  }
  
  // 🌍 Public login method
  public login(password: string): boolean {
    if (this.authenticate(password)) {
      console.log(`βœ… ${this.username} logged in successfully`);
      return true;
    }
    
    console.log(`❌ Login failed for ${this.username}`);
    return false;
  }
  
  // 🌍 Public method to check permissions
  public hasPermission(permission: string): boolean {
    return this.permissions.includes(permission);
  }
  
  // 🌍 Public getters
  public getRole(): string {
    return this.role;
  }
  
  public getLastLogin(): Date | null {
    return this.lastLogin;
  }
  
  // 🌍 Public user info
  public getUserInfo(): string {
    const lastLoginStr = this.lastLogin 
      ? this.lastLogin.toLocaleString() 
      : "Never";
    
    return `
πŸ‘€ ${this.username} (${this.email})
πŸ†” ID: ${this.id}
πŸ‘€ Role: ${this.role}
πŸ”‘ Permissions: ${this.permissions.join(", ")}
⏰ Last Login: ${lastLoginStr}
    `.trim();
  }
}

// πŸ‘” Manager class with additional privileges
class Manager extends User {
  // πŸ‘₯ Protected team management
  protected teamMembers: string[];
  protected managedProjects: string[];
  
  constructor(username: string, email: string, password: string) {
    super(username, email, password);
    this.role = "manager";
    this.permissions = [
      ...this.permissions,
      "view_all_tasks",
      "assign_tasks",
      "approve_tasks",
      "manage_team"
    ];
    this.teamMembers = [];
    this.managedProjects = [];
    
    console.log(`πŸ‘” Manager ${username} account created`);
  }
  
  // 🌍 Public method to add team member
  public addTeamMember(userId: string): void {
    if (!this.teamMembers.includes(userId)) {
      this.teamMembers.push(userId);
      console.log(`πŸ‘₯ Added team member: ${userId}`);
    }
  }
  
  // 🌍 Public method to check if user is team member
  public isTeamMember(userId: string): boolean {
    return this.teamMembers.includes(userId);
  }
}

// πŸš€ Demo the secure task management system
console.log("🏒 Setting up Task Management System...\n");

// Create users
const alice = new User("alice", "[email protected]", "password123");
const bob = new Manager("bob", "[email protected]", "manager456");
const charlie = new User("charlie", "[email protected]", "user789");

// Login users
alice.login("password123");
bob.login("manager456");

// Add team members
bob.addTeamMember(alice.id);

// Create tasks
const task1 = new Task(
  "Implement user authentication",
  "Add secure login system with password hashing",
  alice.username
);

const priorityTask = new PriorityTask(
  "Security audit",
  "Comprehensive security review of the system",
  bob.username,
  [alice.username, charlie.username]
);

console.log("\nπŸ“ Task Operations:");

// Task operations
task1.assignTo(charlie.username, alice.username);
task1.setDueDate(new Date("2025-07-01"), alice.username);
task1.updateStatus("in-progress", charlie.username);

// Priority task operations
priorityTask.approve(alice.username);
priorityTask.assignTo(charlie.username, bob.username);

console.log("\nπŸ“Š Task Information:");
console.log(task1.getTaskInfo(alice.username));
console.log("\n" + priorityTask.getTaskInfo(charlie.username));

console.log("\nπŸ‘€ User Information:");
console.log(alice.getUserInfo());
console.log("\n" + bob.getUserInfo());

// Test encryption
console.log("\nπŸ” Testing Encryption:");
task1.encryptTask(alice.username);
console.log("After encryption:");
console.log(task1.getTaskInfo(charlie.username)); // Should show limited info

task1.decryptTask(alice.username);
console.log("After decryption:");
console.log(task1.getTaskInfo(alice.username));

πŸŽ“ Key Takeaways

Outstanding! You’ve mastered TypeScript access modifiers! Here’s what you can now do:

  • βœ… Use public effectively for clean APIs 🌍
  • βœ… Secure with private for internal implementation πŸ”’
  • βœ… Share with protected for inheritance-friendly design πŸ‘₯
  • βœ… Protect with readonly for immutable data πŸ“–
  • βœ… Design secure systems with proper encapsulation πŸ›‘οΈ
  • βœ… Create maintainable hierarchies with controlled access πŸ—οΈ

Remember: Good access control is the foundation of secure, maintainable code! πŸš€

🀝 Next Steps

Excellent work! πŸŽ‰ You’re now an access control expert!

Continue building your TypeScript mastery:

  1. πŸ’» Complete the secure task management exercise above
  2. πŸ—οΈ Refactor existing code to use proper access modifiers
  3. πŸ“š Next tutorial: Static Members and Methods - Class-Level Functionality
  4. 🌟 Design your own secure class hierarchies!

Remember: Great software is built with careful attention to access control. Keep securing, keep learning! πŸ”


Happy coding! πŸŽ‰πŸ”’βœ¨