+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 38 of 355

๐Ÿ”’ Protected Constructor Pattern: Controlling Instantiation

Master the protected constructor pattern in TypeScript to control how and when objects are created ๐Ÿš€

๐Ÿ’ŽAdvanced
20 min read

Prerequisites

  • Understanding of constructors ๐Ÿ“
  • Knowledge of access modifiers ๐Ÿ”’
  • Familiarity with static methods โšก

What you'll learn

  • Understand protected constructor pattern ๐ŸŽฏ
  • Implement singleton and factory patterns ๐Ÿ—๏ธ
  • Control object creation effectively ๐Ÿ›ก๏ธ
  • Apply advanced instantiation patterns โœจ

๐ŸŽฏ Introduction

Welcome to the fascinating world of controlled instantiation! ๐ŸŽ‰ In this guide, weโ€™ll explore how the protected constructor pattern gives you superpowers over object creation, allowing you to decide exactly how and when instances of your classes come to life.

Youโ€™ll discover how protected constructors are like exclusive VIP passes ๐ŸŽซ - they restrict who can create objects while still allowing inheritance. Whether youโ€™re implementing singletons ๐Ÿ‘‘, building factory patterns ๐Ÿญ, or creating fluent builders ๐Ÿ”ง, understanding protected constructors is essential for advanced TypeScript design patterns.

By the end of this tutorial, youโ€™ll be confidently controlling object creation like a master architect! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Protected Constructors

๐Ÿค” What is the Protected Constructor Pattern?

The protected constructor pattern is like having a private club ๐Ÿฐ where only members (subclasses) and the class itself can create new instances. Think of it as a bouncer at the door who only lets in specific people.

In TypeScript terms, a protected constructor:

  • โœจ Cannot be called with new from outside the class
  • ๐Ÿš€ Can be called by subclasses using super()
  • ๐Ÿ›ก๏ธ Can be called by static methods within the same class
  • ๐Ÿ”ง Enables controlled instantiation patterns

๐Ÿ’ก Why Use Protected Constructors?

Hereโ€™s why developers love protected constructors:

  1. Singleton Pattern ๐Ÿ‘‘: Ensure only one instance exists
  2. Factory Pattern ๐Ÿญ: Control how objects are created
  3. Builder Pattern ๐Ÿ”ง: Create complex objects step by step
  4. Validation ๐Ÿ›ก๏ธ: Ensure objects are always valid

Real-world example: Imagine a database connection pool ๐ŸŠ. You donโ€™t want anyone creating connections directly - they should go through a controlled factory that manages the pool size and connection lifecycle.

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Example

Letโ€™s start with a basic protected constructor:

// ๐Ÿฐ Class with protected constructor
class Castle {
  private name: string;
  private defenders: number;

  // ๐Ÿ”’ Protected constructor - can't be instantiated directly
  protected constructor(name: string, defenders: number) {
    this.name = name;
    this.defenders = defenders;
    console.log(`๐Ÿฐ Castle ${name} constructed with ${defenders} defenders`);
  }

  // ๐Ÿญ Static factory method - the only way to create a castle
  static createCastle(name: string, defenders: number): Castle {
    // ๐Ÿ›ก๏ธ Validation before creation
    if (defenders < 10) {
      throw new Error('A castle needs at least 10 defenders! โš”๏ธ');
    }

    if (name.length < 3) {
      throw new Error('Castle name must be at least 3 characters! ๐Ÿ“');
    }

    // โœ… Create instance using protected constructor
    return new Castle(name, defenders);
  }

  // ๐Ÿฐ Castle methods
  getInfo(): string {
    return `๐Ÿฐ ${this.name} - Defenders: ${this.defenders}`;
  }

  addDefenders(count: number): void {
    this.defenders += count;
    console.log(`โš”๏ธ Added ${count} defenders. Total: ${this.defenders}`);
  }
}

// โŒ This would error: Constructor of class 'Castle' is protected
// const castle1 = new Castle('Winterfell', 100);

// โœ… Correct way - use factory method
const castle2 = Castle.createCastle('Winterfell', 100);
console.log(castle2.getInfo());

// โŒ This throws error - validation fails
try {
  const weakCastle = Castle.createCastle('Hut', 5);
} catch (error) {
  console.log(`โŒ Error: ${error.message}`);
}

๐Ÿ’ก Explanation: The protected constructor prevents direct instantiation, forcing users to go through our validated factory method!

๐ŸŽฏ Singleton Pattern Implementation

Hereโ€™s how to implement the classic singleton pattern:

// ๐Ÿ‘‘ Singleton class - only one instance allowed
class GameManager {
  private static instance: GameManager | null = null;
  private score: number = 0;
  private level: number = 1;
  private playerName: string;

  // ๐Ÿ”’ Protected constructor prevents direct instantiation
  protected constructor(playerName: string) {
    this.playerName = playerName;
    console.log(`๐ŸŽฎ Game Manager initialized for ${playerName}`);
  }

  // ๐Ÿ† Get the single instance
  static getInstance(playerName?: string): GameManager {
    if (!GameManager.instance) {
      if (!playerName) {
        throw new Error('Player name required for first initialization! ๐Ÿ“');
      }
      GameManager.instance = new GameManager(playerName);
    }

    return GameManager.instance;
  }

  // ๐ŸŽฎ Game methods
  addScore(points: number): void {
    this.score += points;
    console.log(`๐ŸŽฏ Score: ${this.score} (+${points})`);
    
    // Level up every 100 points
    const newLevel = Math.floor(this.score / 100) + 1;
    if (newLevel > this.level) {
      this.levelUp();
    }
  }

  private levelUp(): void {
    this.level++;
    console.log(`๐ŸŽ‰ LEVEL UP! Now at level ${this.level}`);
  }

  getStats(): string {
    return `๐Ÿ‘ค ${this.playerName} | ๐ŸŽฏ Score: ${this.score} | ๐Ÿ“Š Level: ${this.level}`;
  }

  // ๐Ÿ”„ Reset the singleton (useful for testing)
  static resetInstance(): void {
    GameManager.instance = null;
    console.log('๐Ÿ”„ Game Manager reset');
  }
}

// ๐ŸŽฎ Using the singleton
const game1 = GameManager.getInstance('Player1');
game1.addScore(50);
game1.addScore(60); // This triggers level up

// ๐ŸŽฏ Same instance returned
const game2 = GameManager.getInstance(); // No name needed - already initialized
console.log(game1 === game2); // true - same instance!

console.log(game2.getStats());

// โŒ Can't create new instance
// const game3 = new GameManager('Player2'); // Error: Constructor is protected

๐Ÿ’ก Practical Examples

๐Ÿญ Example 1: Advanced Factory Pattern

Letโ€™s create a vehicle factory with protected constructors:

// ๐Ÿš— Abstract base vehicle class
abstract class Vehicle {
  protected brand: string;
  protected model: string;
  protected year: number;
  protected mileage: number = 0;

  // ๐Ÿ”’ Protected constructor - only subclasses can call
  protected constructor(brand: string, model: string, year: number) {
    this.brand = brand;
    this.model = model;
    this.year = year;
  }

  abstract getType(): string;
  abstract getMaxSpeed(): number;
  
  getInfo(): string {
    return `${this.year} ${this.brand} ${this.model} (${this.getType()})`;
  }

  drive(distance: number): void {
    this.mileage += distance;
    console.log(`๐Ÿš— Drove ${distance} miles. Total mileage: ${this.mileage}`);
  }
}

// ๐Ÿš— Car implementation
class Car extends Vehicle {
  private doors: number;
  private fuelType: 'gas' | 'electric' | 'hybrid';

  // ๐Ÿ”’ Protected constructor
  protected constructor(brand: string, model: string, year: number, doors: number, fuelType: 'gas' | 'electric' | 'hybrid') {
    super(brand, model, year);
    this.doors = doors;
    this.fuelType = fuelType;
  }

  // ๐Ÿญ Static factory methods for different car types
  static createSedan(brand: string, model: string, year: number): Car {
    console.log('๐Ÿš— Creating sedan...');
    return new Car(brand, model, year, 4, 'gas');
  }

  static createElectricCar(brand: string, model: string, year: number): Car {
    console.log('โšก Creating electric car...');
    return new Car(brand, model, year, 4, 'electric');
  }

  static createSportsCar(brand: string, model: string, year: number): Car {
    console.log('๐ŸŽ๏ธ Creating sports car...');
    return new Car(brand, model, year, 2, 'gas');
  }

  getType(): string {
    return `${this.fuelType} car`;
  }

  getMaxSpeed(): number {
    return this.doors === 2 ? 180 : 120; // Sports cars are faster!
  }

  charge(): void {
    if (this.fuelType === 'electric') {
      console.log('๐Ÿ”‹ Charging electric car...');
    } else {
      console.log('โ›ฝ This car uses gas!');
    }
  }
}

// ๐Ÿ๏ธ Motorcycle implementation
class Motorcycle extends Vehicle {
  private engineSize: number;

  // ๐Ÿ”’ Protected constructor
  protected constructor(brand: string, model: string, year: number, engineSize: number) {
    super(brand, model, year);
    this.engineSize = engineSize;
  }

  // ๐Ÿญ Factory methods for different motorcycle types
  static createSportBike(brand: string, model: string, year: number): Motorcycle {
    console.log('๐Ÿ๏ธ Creating sport bike...');
    return new Motorcycle(brand, model, year, 1000); // 1000cc
  }

  static createCruiser(brand: string, model: string, year: number): Motorcycle {
    console.log('๐Ÿ›ต Creating cruiser...');
    return new Motorcycle(brand, model, year, 1800); // 1800cc
  }

  static createScooter(brand: string, model: string, year: number): Motorcycle {
    console.log('๐Ÿ›ด Creating scooter...');
    return new Motorcycle(brand, model, year, 150); // 150cc
  }

  getType(): string {
    return `${this.engineSize}cc motorcycle`;
  }

  getMaxSpeed(): number {
    if (this.engineSize >= 1000) return 160;
    if (this.engineSize >= 500) return 100;
    return 60; // Scooters
  }

  wheelie(): void {
    if (this.engineSize >= 600) {
      console.log('๐Ÿคธ Performing a wheelie!');
    } else {
      console.log('โŒ Not enough power for a wheelie!');
    }
  }
}

// ๐Ÿญ Master Vehicle Factory
class VehicleFactory {
  private static vehicleCount: number = 0;
  private static vehicles: Map<string, Vehicle> = new Map();

  // ๐Ÿš— Create any type of vehicle
  static createVehicle(type: string, brand: string, model: string, year: number): Vehicle {
    let vehicle: Vehicle;

    switch (type.toLowerCase()) {
      case 'sedan':
        vehicle = Car.createSedan(brand, model, year);
        break;
      case 'electric':
        vehicle = Car.createElectricCar(brand, model, year);
        break;
      case 'sports':
        vehicle = Car.createSportsCar(brand, model, year);
        break;
      case 'sportbike':
        vehicle = Motorcycle.createSportBike(brand, model, year);
        break;
      case 'cruiser':
        vehicle = Motorcycle.createCruiser(brand, model, year);
        break;
      case 'scooter':
        vehicle = Motorcycle.createScooter(brand, model, year);
        break;
      default:
        throw new Error(`Unknown vehicle type: ${type}`);
    }

    // Track created vehicles
    const id = `VEH_${++VehicleFactory.vehicleCount}`;
    VehicleFactory.vehicles.set(id, vehicle);
    console.log(`โœ… Created vehicle ${id}: ${vehicle.getInfo()}`);

    return vehicle;
  }

  // ๐Ÿ“Š Get factory statistics
  static getStats(): void {
    console.log(`\n๐Ÿญ Vehicle Factory Stats:`);
    console.log(`   Total vehicles created: ${VehicleFactory.vehicleCount}`);
    console.log(`   Vehicles in inventory: ${VehicleFactory.vehicles.size}`);
    
    const types = new Map<string, number>();
    VehicleFactory.vehicles.forEach(vehicle => {
      const type = vehicle.getType();
      types.set(type, (types.get(type) || 0) + 1);
    });

    console.log('   By type:');
    types.forEach((count, type) => {
      console.log(`     - ${type}: ${count}`);
    });
  }
}

// ๐ŸŽฎ Let's create some vehicles!
const tesla = VehicleFactory.createVehicle('electric', 'Tesla', 'Model S', 2023);
const ferrari = VehicleFactory.createVehicle('sports', 'Ferrari', '488 GTB', 2023);
const harley = VehicleFactory.createVehicle('cruiser', 'Harley-Davidson', 'Road King', 2023);
const vespa = VehicleFactory.createVehicle('scooter', 'Vespa', 'Primavera', 2023);

// Use the vehicles
console.log('\n๐Ÿš— Vehicle Info:');
console.log(tesla.getInfo() + ' - Max speed: ' + tesla.getMaxSpeed() + ' mph');
console.log(ferrari.getInfo() + ' - Max speed: ' + ferrari.getMaxSpeed() + ' mph');

// Type-specific methods
if (tesla instanceof Car) {
  tesla.charge();
}

if (harley instanceof Motorcycle) {
  harley.wheelie();
}

// Show factory stats
VehicleFactory.getStats();

๐Ÿ”ง Example 2: Fluent Builder Pattern

Letโ€™s create a fluent builder with protected constructor:

// ๐Ÿ  House class with complex construction
class House {
  private bedrooms: number;
  private bathrooms: number;
  private squareFeet: number;
  private hasGarage: boolean;
  private hasPool: boolean;
  private hasBasement: boolean;
  private style: 'modern' | 'traditional' | 'colonial' | 'ranch';
  private address: string;

  // ๐Ÿ”’ Protected constructor - must use builder
  protected constructor(builder: HouseBuilder) {
    this.bedrooms = builder.bedrooms;
    this.bathrooms = builder.bathrooms;
    this.squareFeet = builder.squareFeet;
    this.hasGarage = builder.hasGarage;
    this.hasPool = builder.hasPool;
    this.hasBasement = builder.hasBasement;
    this.style = builder.style;
    this.address = builder.address;
  }

  // ๐Ÿ  House methods
  getDescription(): string {
    const features: string[] = [];
    if (this.hasGarage) features.push('Garage ๐Ÿš—');
    if (this.hasPool) features.push('Pool ๐ŸŠ');
    if (this.hasBasement) features.push('Basement ๐Ÿš๏ธ');

    return `๐Ÿ  ${this.style.charAt(0).toUpperCase() + this.style.slice(1)} House at ${this.address}
    ๐Ÿ“ ${this.squareFeet} sq ft | ๐Ÿ›๏ธ ${this.bedrooms} bed | ๐Ÿšฟ ${this.bathrooms} bath
    โœจ Features: ${features.length > 0 ? features.join(', ') : 'Standard'}`;
  }

  calculatePrice(): number {
    let basePrice = this.squareFeet * 150; // $150 per sq ft
    
    // Style multipliers
    const styleMultipliers = {
      modern: 1.3,
      traditional: 1.0,
      colonial: 1.2,
      ranch: 0.9
    };
    
    basePrice *= styleMultipliers[this.style];

    // Add feature costs
    if (this.hasGarage) basePrice += 25000;
    if (this.hasPool) basePrice += 50000;
    if (this.hasBasement) basePrice += 30000;

    return Math.round(basePrice);
  }

  // ๐Ÿ—๏ธ Static method to create builder
  static create(): HouseBuilder {
    return new HouseBuilder();
  }
}

// ๐Ÿ”ง House Builder class
class HouseBuilder {
  // Internal properties (package visible to House)
  bedrooms: number = 1;
  bathrooms: number = 1;
  squareFeet: number = 1000;
  hasGarage: boolean = false;
  hasPool: boolean = false;
  hasBasement: boolean = false;
  style: 'modern' | 'traditional' | 'colonial' | 'ranch' = 'traditional';
  address: string = '';

  // ๐Ÿ”’ Protected to prevent direct instantiation
  protected constructor() {}

  // ๐Ÿ—๏ธ Fluent builder methods
  withBedrooms(count: number): HouseBuilder {
    if (count < 1 || count > 10) {
      throw new Error('Bedrooms must be between 1 and 10! ๐Ÿ›๏ธ');
    }
    this.bedrooms = count;
    return this;
  }

  withBathrooms(count: number): HouseBuilder {
    if (count < 1 || count > 8) {
      throw new Error('Bathrooms must be between 1 and 8! ๐Ÿšฟ');
    }
    this.bathrooms = count;
    return this;
  }

  withSquareFeet(size: number): HouseBuilder {
    if (size < 500 || size > 20000) {
      throw new Error('Size must be between 500 and 20,000 sq ft! ๐Ÿ“');
    }
    this.squareFeet = size;
    return this;
  }

  withGarage(): HouseBuilder {
    this.hasGarage = true;
    return this;
  }

  withPool(): HouseBuilder {
    this.hasPool = true;
    return this;
  }

  withBasement(): HouseBuilder {
    this.hasBasement = true;
    return this;
  }

  withStyle(style: 'modern' | 'traditional' | 'colonial' | 'ranch'): HouseBuilder {
    this.style = style;
    return this;
  }

  atAddress(address: string): HouseBuilder {
    if (!address || address.length < 5) {
      throw new Error('Valid address required! ๐Ÿ“');
    }
    this.address = address;
    return this;
  }

  // ๐Ÿ  Build the house - validation and creation
  build(): House {
    // Validation
    if (!this.address) {
      throw new Error('Address is required to build a house! ๐Ÿ“');
    }

    if (this.bathrooms > this.bedrooms + 1) {
      throw new Error('Too many bathrooms for the number of bedrooms! ๐Ÿšฟ');
    }

    if (this.hasPool && this.squareFeet < 2000) {
      throw new Error('House too small for a pool! Need at least 2000 sq ft ๐ŸŠ');
    }

    // Create house using protected constructor
    console.log('๐Ÿ—๏ธ Building house...');
    const house = new (House as any)(this); // TypeScript hack to access protected constructor
    console.log('โœ… House built successfully!');
    
    return house;
  }

  // ๐Ÿก Preset configurations
  static starterHome(): HouseBuilder {
    return new HouseBuilder()
      .withBedrooms(2)
      .withBathrooms(1)
      .withSquareFeet(1200)
      .withStyle('ranch');
  }

  static familyHome(): HouseBuilder {
    return new HouseBuilder()
      .withBedrooms(4)
      .withBathrooms(3)
      .withSquareFeet(2500)
      .withGarage()
      .withBasement()
      .withStyle('traditional');
  }

  static luxuryHome(): HouseBuilder {
    return new HouseBuilder()
      .withBedrooms(5)
      .withBathrooms(4)
      .withSquareFeet(5000)
      .withGarage()
      .withPool()
      .withBasement()
      .withStyle('modern');
  }
}

// ๐ŸŽฎ Let's build some houses!
console.log('๐Ÿ—๏ธ HOUSE BUILDING DEMO ๐Ÿ—๏ธ\n');

// Custom build
const customHouse = House.create()
  .withBedrooms(3)
  .withBathrooms(2)
  .withSquareFeet(1800)
  .withGarage()
  .withStyle('colonial')
  .atAddress('123 Main Street')
  .build();

console.log(customHouse.getDescription());
console.log(`๐Ÿ’ฐ Price: $${customHouse.calculatePrice().toLocaleString()}\n`);

// Using presets
const starterHouse = HouseBuilder.starterHome()
  .atAddress('456 Oak Avenue')
  .build();

console.log(starterHouse.getDescription());
console.log(`๐Ÿ’ฐ Price: $${starterHouse.calculatePrice().toLocaleString()}\n`);

const luxuryHouse = HouseBuilder.luxuryHome()
  .atAddress('789 Luxury Lane')
  .build();

console.log(luxuryHouse.getDescription());
console.log(`๐Ÿ’ฐ Price: $${luxuryHouse.calculatePrice().toLocaleString()}\n`);

// โŒ Try invalid configurations
try {
  const invalidHouse = House.create()
    .withBedrooms(2)
    .withBathrooms(5) // Too many bathrooms!
    .atAddress('999 Error Street')
    .build();
} catch (error) {
  console.log(`โŒ Build failed: ${error.message}`);
}

๐Ÿ” Example 3: Connection Pool Pattern

Letโ€™s create a database connection pool with controlled instantiation:

// ๐Ÿ”Œ Database connection class
class DatabaseConnection {
  private id: string;
  private inUse: boolean = false;
  private createdAt: Date;
  private lastUsed: Date;

  // ๐Ÿ”’ Protected constructor - only pool can create
  protected constructor(id: string) {
    this.id = id;
    this.createdAt = new Date();
    this.lastUsed = new Date();
    console.log(`๐Ÿ”Œ Connection ${id} created`);
  }

  // Friend class pattern - only ConnectionPool can create
  static createForPool(id: string): DatabaseConnection {
    return new DatabaseConnection(id);
  }

  // ๐Ÿ”Œ Connection methods
  query(sql: string): void {
    if (!this.inUse) {
      throw new Error('Connection not acquired! ๐Ÿ”’');
    }
    console.log(`๐Ÿ“Š [${this.id}] Executing: ${sql}`);
    this.lastUsed = new Date();
  }

  acquire(): void {
    if (this.inUse) {
      throw new Error('Connection already in use! โš ๏ธ');
    }
    this.inUse = true;
    console.log(`โœ… Connection ${this.id} acquired`);
  }

  release(): void {
    this.inUse = false;
    console.log(`๐Ÿ”„ Connection ${this.id} released`);
  }

  isAvailable(): boolean {
    return !this.inUse;
  }

  getInfo(): string {
    const idleTime = Date.now() - this.lastUsed.getTime();
    return `Connection ${this.id}: ${this.inUse ? 'IN USE' : 'AVAILABLE'} (idle: ${Math.round(idleTime / 1000)}s)`;
  }
}

// ๐ŸŠ Connection Pool with singleton pattern
class ConnectionPool {
  private static instance: ConnectionPool | null = null;
  private connections: DatabaseConnection[] = [];
  private maxConnections: number;
  private activeConnections: number = 0;

  // ๐Ÿ”’ Protected constructor for singleton
  protected constructor(maxConnections: number = 10) {
    this.maxConnections = maxConnections;
    console.log(`๐ŸŠ Connection pool initialized (max: ${maxConnections})`);
    
    // Pre-create some connections
    this.createInitialConnections();
  }

  // ๐ŸŠ Get pool instance
  static getInstance(maxConnections?: number): ConnectionPool {
    if (!ConnectionPool.instance) {
      ConnectionPool.instance = new ConnectionPool(maxConnections);
    }
    return ConnectionPool.instance;
  }

  private createInitialConnections(): void {
    const initialCount = Math.min(3, this.maxConnections);
    for (let i = 0; i < initialCount; i++) {
      const conn = DatabaseConnection.createForPool(`CONN_${i + 1}`);
      this.connections.push(conn);
    }
    console.log(`๐Ÿ”Œ Created ${initialCount} initial connections`);
  }

  // ๐Ÿ”Œ Get a connection from the pool
  async getConnection(): Promise<DatabaseConnection> {
    console.log('๐Ÿ” Requesting connection from pool...');

    // Find available connection
    let connection = this.connections.find(conn => conn.isAvailable());

    if (!connection) {
      // Create new connection if under limit
      if (this.connections.length < this.maxConnections) {
        const newId = `CONN_${this.connections.length + 1}`;
        connection = DatabaseConnection.createForPool(newId);
        this.connections.push(connection);
        console.log(`๐Ÿ†• Created new connection: ${newId}`);
      } else {
        // Wait for available connection
        console.log('โณ Pool exhausted, waiting for available connection...');
        connection = await this.waitForConnection();
      }
    }

    connection.acquire();
    this.activeConnections++;
    return connection;
  }

  // โณ Wait for connection to become available
  private async waitForConnection(): Promise<DatabaseConnection> {
    return new Promise((resolve) => {
      const checkInterval = setInterval(() => {
        const available = this.connections.find(conn => conn.isAvailable());
        if (available) {
          clearInterval(checkInterval);
          resolve(available);
        }
      }, 100);
    });
  }

  // ๐Ÿ”„ Return connection to pool
  releaseConnection(connection: DatabaseConnection): void {
    connection.release();
    this.activeConnections--;
    console.log(`๐Ÿ”„ Connection returned to pool. Active: ${this.activeConnections}/${this.connections.length}`);
  }

  // ๐Ÿ“Š Pool statistics
  getStats(): void {
    console.log('\n๐Ÿ“Š Connection Pool Stats:');
    console.log(`   Total connections: ${this.connections.length}/${this.maxConnections}`);
    console.log(`   Active connections: ${this.activeConnections}`);
    console.log(`   Available connections: ${this.connections.length - this.activeConnections}`);
    console.log('\n   Connection details:');
    this.connections.forEach(conn => {
      console.log(`   - ${conn.getInfo()}`);
    });
  }
}

// ๐Ÿ” Scoped connection helper
class ScopedConnection {
  private connection: DatabaseConnection;
  private pool: ConnectionPool;
  private released: boolean = false;

  // ๐Ÿ”’ Protected constructor
  protected constructor(connection: DatabaseConnection, pool: ConnectionPool) {
    this.connection = connection;
    this.pool = pool;
  }

  // ๐Ÿญ Factory method
  static async create(): Promise<ScopedConnection> {
    const pool = ConnectionPool.getInstance();
    const connection = await pool.getConnection();
    return new ScopedConnection(connection, pool);
  }

  // ๐Ÿ“Š Execute query
  async execute(sql: string): Promise<void> {
    if (this.released) {
      throw new Error('Connection already released! ๐Ÿ”’');
    }
    this.connection.query(sql);
  }

  // ๐Ÿ”„ Auto-release
  async release(): Promise<void> {
    if (!this.released) {
      this.pool.releaseConnection(this.connection);
      this.released = true;
    }
  }

  // ๐ŸŽฏ Execute with auto-release
  static async withConnection<T>(
    operation: (conn: ScopedConnection) => Promise<T>
  ): Promise<T> {
    const scopedConn = await ScopedConnection.create();
    try {
      return await operation(scopedConn);
    } finally {
      await scopedConn.release();
    }
  }
}

// ๐ŸŽฎ Demo the connection pool
async function demoConnectionPool() {
  console.log('๐Ÿ” CONNECTION POOL DEMO ๐Ÿ”\n');

  const pool = ConnectionPool.getInstance(5); // Max 5 connections

  // Simulate multiple database operations
  const operations = [
    async () => {
      const conn = await pool.getConnection();
      conn.query('SELECT * FROM users');
      await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate work
      pool.releaseConnection(conn);
    },
    async () => {
      // Using scoped connection
      await ScopedConnection.withConnection(async (conn) => {
        await conn.execute('INSERT INTO logs VALUES (...)');
        await new Promise(resolve => setTimeout(resolve, 500));
      });
    },
    async () => {
      const conn = await pool.getConnection();
      conn.query('UPDATE products SET price = price * 1.1');
      await new Promise(resolve => setTimeout(resolve, 800));
      pool.releaseConnection(conn);
    }
  ];

  // Run operations concurrently
  console.log('๐Ÿš€ Running concurrent database operations...\n');
  await Promise.all(operations.map(op => op()));

  // Show pool stats
  pool.getStats();

  // Demonstrate pool exhaustion
  console.log('\n๐Ÿ”ฅ Testing pool exhaustion...');
  const connections: DatabaseConnection[] = [];
  
  try {
    // Get all available connections
    for (let i = 0; i < 6; i++) { // Try to get more than max
      const conn = await pool.getConnection();
      connections.push(conn);
    }
  } catch (error) {
    console.log(`โŒ Error: ${error.message}`);
  }

  // Release connections
  connections.forEach(conn => pool.releaseConnection(conn));
  
  pool.getStats();
}

// Run the demo
demoConnectionPool();

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Registry Pattern

Using protected constructors with a registry:

// ๐Ÿ“š Plugin registry with controlled instantiation
abstract class Plugin {
  protected name: string;
  protected version: string;
  private static registry: Map<string, Plugin> = new Map();

  // ๐Ÿ”’ Protected constructor
  protected constructor(name: string, version: string) {
    this.name = name;
    this.version = version;
  }

  // ๐Ÿ“ Register plugin
  protected register(): void {
    const key = `${this.name}@${this.version}`;
    if (Plugin.registry.has(key)) {
      throw new Error(`Plugin ${key} already registered! ๐Ÿšซ`);
    }
    Plugin.registry.set(key, this);
    console.log(`โœ… Registered plugin: ${key}`);
  }

  // ๐Ÿ” Get plugin from registry
  static getPlugin(name: string, version: string = 'latest'): Plugin | undefined {
    if (version === 'latest') {
      // Find latest version
      const versions = Array.from(Plugin.registry.keys())
        .filter(key => key.startsWith(`${name}@`))
        .map(key => key.split('@')[1])
        .sort();
      
      if (versions.length > 0) {
        version = versions[versions.length - 1];
      }
    }
    
    return Plugin.registry.get(`${name}@${version}`);
  }

  // ๐Ÿ“Š List all plugins
  static listPlugins(): void {
    console.log('๐Ÿ“š Registered Plugins:');
    Plugin.registry.forEach((plugin, key) => {
      console.log(`  - ${key}: ${plugin.getDescription()}`);
    });
  }

  abstract execute(): void;
  abstract getDescription(): string;
}

// ๐Ÿ” Search plugin implementation
class SearchPlugin extends Plugin {
  private static instances: Map<string, SearchPlugin> = new Map();

  // ๐Ÿ”’ Protected constructor
  protected constructor(version: string) {
    super('search', version);
  }

  // ๐Ÿญ Factory with version control
  static create(version: string): SearchPlugin {
    const key = `search@${version}`;
    
    if (!SearchPlugin.instances.has(key)) {
      const plugin = new SearchPlugin(version);
      plugin.register();
      SearchPlugin.instances.set(key, plugin);
    }
    
    return SearchPlugin.instances.get(key)!;
  }

  execute(): void {
    console.log(`๐Ÿ” Executing search plugin v${this.version}`);
  }

  getDescription(): string {
    return `Full-text search capabilities`;
  }
}

// ๐Ÿ“Š Analytics plugin
class AnalyticsPlugin extends Plugin {
  private trackingId: string;

  // ๐Ÿ”’ Protected constructor
  protected constructor(version: string, trackingId: string) {
    super('analytics', version);
    this.trackingId = trackingId;
  }

  // ๐Ÿญ Singleton per tracking ID
  private static instances: Map<string, AnalyticsPlugin> = new Map();

  static create(version: string, trackingId: string): AnalyticsPlugin {
    const key = `${trackingId}@${version}`;
    
    if (!AnalyticsPlugin.instances.has(key)) {
      const plugin = new AnalyticsPlugin(version, trackingId);
      plugin.register();
      AnalyticsPlugin.instances.set(key, plugin);
    }
    
    return AnalyticsPlugin.instances.get(key)!;
  }

  execute(): void {
    console.log(`๐Ÿ“Š Tracking with ${this.trackingId} (v${this.version})`);
  }

  getDescription(): string {
    return `Analytics tracking for ${this.trackingId}`;
  }
}

๐Ÿ—๏ธ Advanced Topic 2: Multi-Stage Builder

Complex builders with protected constructors:

// ๐ŸŽฌ Movie builder with type-safe stages
class Movie {
  title: string;
  director: string;
  year: number;
  genre: string;
  runtime: number;
  cast: string[];
  budget?: number;
  rating?: string;

  // ๐Ÿ”’ Protected constructor
  protected constructor(builder: CompletedMovieBuilder) {
    this.title = builder.title;
    this.director = builder.director;
    this.year = builder.year;
    this.genre = builder.genre;
    this.runtime = builder.runtime;
    this.cast = builder.cast;
    this.budget = builder.budget;
    this.rating = builder.rating;
  }

  getInfo(): string {
    return `๐ŸŽฌ "${this.title}" (${this.year})
    ๐ŸŽญ Director: ${this.director}
    ๐ŸŽฏ Genre: ${this.genre}
    โฑ๏ธ Runtime: ${this.runtime} minutes
    ๐Ÿ‘ฅ Cast: ${this.cast.join(', ')}
    ${this.budget ? `๐Ÿ’ฐ Budget: $${this.budget.toLocaleString()}` : ''}
    ${this.rating ? `โญ Rating: ${this.rating}` : ''}`;
  }
}

// Stage interfaces
interface NeedsTitleStage {
  withTitle(title: string): NeedsDirectorStage;
}

interface NeedsDirectorStage {
  withDirector(director: string): NeedsYearStage;
}

interface NeedsYearStage {
  withYear(year: number): NeedsGenreStage;
}

interface NeedsGenreStage {
  withGenre(genre: string): NeedsRuntimeStage;
}

interface NeedsRuntimeStage {
  withRuntime(minutes: number): NeedsCastStage;
}

interface NeedsCastStage {
  withCast(...actors: string[]): OptionalStage;
}

interface OptionalStage {
  withBudget(amount: number): OptionalStage;
  withRating(rating: string): OptionalStage;
  build(): Movie;
}

// Complete builder type
interface CompletedMovieBuilder {
  title: string;
  director: string;
  year: number;
  genre: string;
  runtime: number;
  cast: string[];
  budget?: number;
  rating?: string;
}

// ๐Ÿ—๏ธ Multi-stage builder implementation
class MovieBuilder implements 
  NeedsTitleStage, 
  NeedsDirectorStage, 
  NeedsYearStage,
  NeedsGenreStage,
  NeedsRuntimeStage,
  NeedsCastStage,
  OptionalStage,
  CompletedMovieBuilder {
  
  title!: string;
  director!: string;
  year!: number;
  genre!: string;
  runtime!: number;
  cast!: string[];
  budget?: number;
  rating?: string;

  // ๐Ÿ”’ Protected constructor
  protected constructor() {}

  // ๐Ÿญ Entry point
  static create(): NeedsTitleStage {
    return new MovieBuilder();
  }

  withTitle(title: string): NeedsDirectorStage {
    this.title = title;
    return this;
  }

  withDirector(director: string): NeedsYearStage {
    this.director = director;
    return this;
  }

  withYear(year: number): NeedsGenreStage {
    if (year < 1900 || year > new Date().getFullYear() + 5) {
      throw new Error('Invalid year! ๐Ÿ“…');
    }
    this.year = year;
    return this;
  }

  withGenre(genre: string): NeedsRuntimeStage {
    this.genre = genre;
    return this;
  }

  withRuntime(minutes: number): NeedsCastStage {
    if (minutes < 1 || minutes > 600) {
      throw new Error('Runtime must be between 1 and 600 minutes! โฑ๏ธ');
    }
    this.runtime = minutes;
    return this;
  }

  withCast(...actors: string[]): OptionalStage {
    if (actors.length === 0) {
      throw new Error('At least one cast member required! ๐Ÿ‘ฅ');
    }
    this.cast = actors;
    return this;
  }

  withBudget(amount: number): OptionalStage {
    this.budget = amount;
    return this;
  }

  withRating(rating: string): OptionalStage {
    this.rating = rating;
    return this;
  }

  build(): Movie {
    return new (Movie as any)(this);
  }
}

// ๐ŸŽฎ Demo the multi-stage builder
const movie1 = MovieBuilder.create()
  .withTitle('The TypeScript Chronicles')
  .withDirector('Code Cameron')
  .withYear(2024)
  .withGenre('Tech Thriller')
  .withRuntime(120)
  .withCast('Dev Patel', 'Emma Console', 'Chris Compile')
  .withBudget(50000000)
  .withRating('PG-13')
  .build();

console.log(movie1.getInfo());

// Type safety prevents skipping stages
// const movie2 = MovieBuilder.create()
//   .withTitle('Bad Movie')
//   .build(); // โŒ Error: Property 'build' does not exist

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Forgetting to Make Constructor Protected

// โŒ Wrong - public constructor defeats the purpose!
class ConfigManager {
  private static instance: ConfigManager;
  
  constructor() { // Should be protected!
    // Anyone can create instances
  }
  
  static getInstance(): ConfigManager {
    // Singleton pattern broken!
    return this.instance || (this.instance = new ConfigManager());
  }
}

// โœ… Correct - protected constructor
class SecureConfigManager {
  private static instance: SecureConfigManager;
  
  protected constructor() { // โœ… Protected!
    console.log('Config manager initialized');
  }
  
  static getInstance(): SecureConfigManager {
    return this.instance || (this.instance = new SecureConfigManager());
  }
}

๐Ÿคฏ Pitfall 2: Accessing Protected Constructor from Wrong Context

class Base {
  protected constructor() {}
}

class Derived extends Base {
  constructor() {
    super(); // โœ… OK - can call protected constructor
  }
  
  // โŒ Wrong - can't create parent instance
  createParent(): Base {
    return new Base(); // Error: Constructor is protected
  }
  
  // โœ… Correct - use factory method
  static createBase(): Base {
    // Would need a factory method in Base class
    throw new Error('Use Base factory method');
  }
}

๐Ÿ”„ Pitfall 3: Breaking Singleton with Inheritance

// โŒ Problematic - inheritance breaks singleton
class SingletonBase {
  protected static instance: SingletonBase;
  protected constructor() {}
  
  static getInstance(): SingletonBase {
    return this.instance || (this.instance = new SingletonBase());
  }
}

class SingletonDerived extends SingletonBase {
  // Now we have TWO singletons!
}

// โœ… Better - composition over inheritance
class SingletonService {
  private static instance: SingletonService;
  
  private constructor() {} // Private prevents inheritance
  
  static getInstance(): SingletonService {
    return this.instance || (this.instance = new SingletonService());
  }
}

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Clear Intent: Make it obvious why constructor is protected
  2. ๐Ÿญ Provide Factory Methods: Always offer a way to create instances
  3. ๐Ÿ“ Document Creation: Explain how to properly instantiate
  4. ๐Ÿ›ก๏ธ Validate in Factories: Put validation in factory methods
  5. ๐Ÿ”’ Consider Private: Use private constructor if no inheritance needed

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Configuration System

Create a configuration system with controlled instantiation:

๐Ÿ“‹ Requirements:

  • โœ… Protected constructor for Config class
  • ๐ŸŒ Environment-specific configs (dev, staging, prod)
  • ๐Ÿ” Singleton pattern for each environment
  • ๐Ÿญ Factory methods with validation
  • ๐Ÿ”„ Config reloading capability

๐Ÿš€ Bonus Points:

  • Add config versioning
  • Implement config inheritance
  • Create config change notifications

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐Ÿ”ง Configuration system with protected constructor
abstract class Config {
  protected environment: string;
  protected version: string;
  protected data: Map<string, any> = new Map();
  protected loadedAt: Date;
  private listeners: ((key: string, value: any) => void)[] = [];

  // ๐Ÿ”’ Protected constructor
  protected constructor(environment: string, version: string) {
    this.environment = environment;
    this.version = version;
    this.loadedAt = new Date();
  }

  // ๐ŸŽฏ Abstract methods
  protected abstract loadDefaults(): void;
  protected abstract validate(): boolean;

  // ๐Ÿ“Š Config methods
  get<T>(key: string, defaultValue?: T): T {
    return this.data.has(key) ? this.data.get(key) : defaultValue;
  }

  set(key: string, value: any): void {
    const oldValue = this.data.get(key);
    this.data.set(key, value);
    
    if (oldValue !== value) {
      this.notifyListeners(key, value);
    }
  }

  has(key: string): boolean {
    return this.data.has(key);
  }

  // ๐Ÿ”” Change notifications
  onChange(listener: (key: string, value: any) => void): void {
    this.listeners.push(listener);
  }

  private notifyListeners(key: string, value: any): void {
    this.listeners.forEach(listener => listener(key, value));
  }

  // ๐Ÿ“‹ Get config info
  getInfo(): string {
    return `โš™๏ธ ${this.environment.toUpperCase()} Config v${this.version}
    ๐Ÿ“… Loaded: ${this.loadedAt.toLocaleString()}
    ๐Ÿ“Š Entries: ${this.data.size}`;
  }

  // ๐Ÿ”„ Reload configuration
  reload(): void {
    console.log(`๐Ÿ”„ Reloading ${this.environment} configuration...`);
    this.data.clear();
    this.loadDefaults();
    
    if (!this.validate()) {
      throw new Error('Configuration validation failed! โŒ');
    }
    
    this.loadedAt = new Date();
    console.log('โœ… Configuration reloaded successfully');
  }
}

// ๐Ÿญ Development configuration
class DevelopmentConfig extends Config {
  private static instance: DevelopmentConfig | null = null;

  // ๐Ÿ”’ Protected constructor
  protected constructor() {
    super('development', '1.0.0');
    this.loadDefaults();
    
    if (!this.validate()) {
      throw new Error('Invalid development configuration! โŒ');
    }
  }

  // ๐Ÿ† Singleton getter
  static getInstance(): DevelopmentConfig {
    if (!DevelopmentConfig.instance) {
      DevelopmentConfig.instance = new DevelopmentConfig();
      console.log('๐Ÿ”ง Development configuration initialized');
    }
    return DevelopmentConfig.instance;
  }

  protected loadDefaults(): void {
    // Development defaults
    this.set('api.url', 'http://localhost:3000');
    this.set('api.timeout', 5000);
    this.set('debug', true);
    this.set('logging.level', 'debug');
    this.set('cache.enabled', false);
    this.set('features.experimental', true);
  }

  protected validate(): boolean {
    // Development validation is lenient
    return this.has('api.url') && this.has('debug');
  }

  // ๐Ÿ”ง Dev-specific methods
  enableAllFeatures(): void {
    console.log('๐Ÿš€ Enabling all experimental features for development');
    this.set('features.experimental', true);
    this.set('features.beta', true);
    this.set('features.alpha', true);
  }
}

// ๐Ÿข Production configuration
class ProductionConfig extends Config {
  private static instance: ProductionConfig | null = null;
  private securityKey: string;

  // ๐Ÿ”’ Protected constructor with security
  protected constructor(securityKey: string) {
    super('production', '1.0.0');
    
    if (!this.validateSecurityKey(securityKey)) {
      throw new Error('Invalid security key! ๐Ÿ”');
    }
    
    this.securityKey = securityKey;
    this.loadDefaults();
    
    if (!this.validate()) {
      throw new Error('Invalid production configuration! โŒ');
    }
  }

  // ๐Ÿ† Singleton with security
  static getInstance(securityKey?: string): ProductionConfig {
    if (!ProductionConfig.instance) {
      if (!securityKey) {
        throw new Error('Security key required for first initialization! ๐Ÿ”');
      }
      ProductionConfig.instance = new ProductionConfig(securityKey);
      console.log('๐Ÿข Production configuration initialized');
    }
    return ProductionConfig.instance;
  }

  private validateSecurityKey(key: string): boolean {
    // Simple validation - in reality would be more complex
    return key.length >= 32 && key.includes('-PROD-');
  }

  protected loadDefaults(): void {
    // Production defaults
    this.set('api.url', 'https://api.production.com');
    this.set('api.timeout', 30000);
    this.set('debug', false);
    this.set('logging.level', 'error');
    this.set('cache.enabled', true);
    this.set('cache.ttl', 3600);
    this.set('security.https', true);
    this.set('security.headers.hsts', true);
    this.set('features.experimental', false);
  }

  protected validate(): boolean {
    // Strict production validation
    return this.has('api.url') &&
           this.get('api.url').startsWith('https://') &&
           this.get('debug') === false &&
           this.get('security.https') === true;
  }

  // ๐Ÿ” Production-specific methods
  getSecureValue(key: string): any {
    console.log(`๐Ÿ” Secure access to ${key}`);
    return this.get(key);
  }
}

// ๐ŸŒ Configuration manager
class ConfigurationManager {
  private static configs: Map<string, Config> = new Map();

  // ๐Ÿญ Get configuration for environment
  static getConfig(environment: 'development' | 'staging' | 'production', securityKey?: string): Config {
    if (!this.configs.has(environment)) {
      let config: Config;
      
      switch (environment) {
        case 'development':
          config = DevelopmentConfig.getInstance();
          break;
        case 'production':
          config = ProductionConfig.getInstance(securityKey);
          break;
        case 'staging':
          // Staging inherits from production with overrides
          config = this.createStagingConfig(securityKey);
          break;
        default:
          throw new Error(`Unknown environment: ${environment}`);
      }
      
      this.configs.set(environment, config);
    }
    
    return this.configs.get(environment)!;
  }

  // ๐ŸŽญ Create staging config (hybrid)
  private static createStagingConfig(securityKey?: string): Config {
    // For demo, we'll create a custom staging config
    class StagingConfig extends ProductionConfig {
      protected constructor(securityKey: string) {
        super(securityKey);
        this.environment = 'staging';
        
        // Override some production settings
        this.set('api.url', 'https://api.staging.com');
        this.set('debug', true); // Enable debug in staging
        this.set('features.beta', true); // Enable beta features
      }
      
      static getInstance(securityKey?: string): StagingConfig {
        return new StagingConfig(securityKey || 'staging-key-PROD-test');
      }
    }
    
    return StagingConfig.getInstance(securityKey);
  }

  // ๐Ÿ“Š Show all configurations
  static showConfigs(): void {
    console.log('\n๐Ÿ“Š Loaded Configurations:');
    this.configs.forEach((config, env) => {
      console.log(`\n${config.getInfo()}`);
    });
  }
}

// ๐ŸŽฎ Demo the configuration system
function demoConfigSystem() {
  console.log('๐Ÿ”ง CONFIGURATION SYSTEM DEMO ๐Ÿ”ง\n');

  // Development config
  const devConfig = ConfigurationManager.getConfig('development');
  console.log(devConfig.getInfo());
  console.log('API URL:', devConfig.get('api.url'));
  console.log('Debug:', devConfig.get('debug'));

  // Change listener
  devConfig.onChange((key, value) => {
    console.log(`๐Ÿ”” Config changed: ${key} = ${value}`);
  });

  // Modify config
  devConfig.set('api.timeout', 10000);

  // Production config (with security)
  try {
    const prodConfig = ConfigurationManager.getConfig('production', 'super-secret-key-PROD-2024');
    console.log('\n' + prodConfig.getInfo());
    console.log('API URL:', prodConfig.get('api.url'));
    console.log('HTTPS:', prodConfig.get('security.https'));
  } catch (error) {
    console.log(`โŒ Production error: ${error.message}`);
  }

  // Staging config
  const stagingConfig = ConfigurationManager.getConfig('staging');
  console.log('\n' + stagingConfig.getInfo());
  console.log('API URL:', stagingConfig.get('api.url'));
  console.log('Debug:', stagingConfig.get('debug'));
  console.log('Beta Features:', stagingConfig.get('features.beta'));

  // Show all configs
  ConfigurationManager.showConfigs();

  // Reload configuration
  console.log('\n๐Ÿ”„ Reloading development config...');
  devConfig.reload();
  
  // Type-safe config access
  interface ApiConfig {
    url: string;
    timeout: number;
  }
  
  const apiConfig: ApiConfig = {
    url: devConfig.get<string>('api.url'),
    timeout: devConfig.get<number>('api.timeout')
  };
  
  console.log('\n๐Ÿ“ก API Configuration:', apiConfig);
}

// Run the demo
demoConfigSystem();

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered the protected constructor pattern! Hereโ€™s what you can now do:

  • โœ… Control instantiation with protected constructors ๐Ÿ”’
  • โœ… Implement singleton pattern correctly ๐Ÿ‘‘
  • โœ… Create sophisticated factory methods ๐Ÿญ
  • โœ… Build fluent builders with type safety ๐Ÿ”ง
  • โœ… Design robust object creation patterns ๐Ÿš€

Remember: Protected constructors give you the power to control the โ€œwhenโ€ and โ€œhowโ€ of object creation! ๐ŸŽฏ

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered protected constructor patterns!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the configuration system exercise above
  2. ๐Ÿ—๏ธ Implement your own controlled instantiation patterns
  3. ๐Ÿ“š Move on to our next tutorial: Interfaces in TypeScript: Defining Object Shapes
  4. ๐ŸŒŸ Experiment with combining protected constructors and factory patterns!

Remember: Great software design often starts with controlling how objects are born. Keep building, keep controlling, and create amazing patterns! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿš€โœจ