Skip to content
intermediate Phase 6 · TypeScript Design Patterns

Singleton & Factory Patterns

Implement creational patterns with TypeScript classes and interfaces.

1h
0 problems
Topic Progress 0%

Singleton Pattern

Singleton Pattern

Basic Singleton

class Database {
  private static instance: Database;
  private connected: boolean = false;

  private constructor(private config: DbConfig) {}

  static getInstance(config?: DbConfig): Database {
    if (!Database.instance) {
      if (!config) throw new Error('Config required for first initialization');
      Database.instance = new Database(config);
    }
    return Database.instance;
  }

  async connect(): Promise<void> {
    if (this.connected) return;
    console.log('Connecting to database...');
    this.connected = true;
  }

  query<T>(sql: string): T[] {
    if (!this.connected) throw new Error('Not connected');
    return [];
  }
}

// Usage
const db = Database.getInstance({ host: 'localhost', port: 5432 });
await db.connect();
const users = db.query<User>('SELECT * FROM users');

Module-Level Singleton

// logger.ts - simpler approach
interface Logger {
  info(msg: string): void;
  error(msg: string, err?: Error): void;
}

class ConsoleLogger implements Logger {
  info(msg: string): void {
    console.log(`[INFO] ${msg}`);
  }
  error(msg: string, err?: Error): void {
    console.error(`[ERROR] ${msg}`, err);
  }
}

// Module exports singleton instance
export const logger = new ConsoleLogger();

Factory Pattern

Factory Pattern

Simple Factory

interface Notification {
  send(message: string): void;
}

class EmailNotification implements Notification {
  send(message: string): void {
    console.log(`Email: ${message}`);
  }
}

class SMSNotification implements Notification {
  send(message: string): void {
    console.log(`SMS: ${message}`);
  }
}

class PushNotification implements Notification {
  send(message: string): void {
    console.log(`Push: ${message}`);
  }
}

class NotificationFactory {
  static create(type: 'email' | 'sms' | 'push'): Notification {
    switch (type) {
      case 'email': return new EmailNotification();
      case 'sms': return new SMSNotification();
      case 'push': return new PushNotification();
    }
  }
}

const notifier = NotificationFactory.create('email');
notifier.send('Hello!');

Abstract Factory

interface Button {
  render(): string;
  onClick(handler: () => void): void;
}

interface Input {
  render(): string;
  getValue(): string;
}

interface UIFactory {
  createButton(): Button;
  createInput(): Input;
}

class MaterialButton implements Button {
  render(): string { return '<button class="mdc-button"></button>'; }
  onClick(handler: () => void): void { /* ... */ }
}

class MaterialInput implements Input {
  render(): string { return '<input class="mdc-textfield" />'; }
  getValue(): string { return ''; }
}

class MaterialFactory implements UIFactory {
  createButton(): Button { return new MaterialButton(); }
  createInput(): Input { return new MaterialInput(); }
}

Factory Method in Classes

abstract class PaymentProcessor {
  abstract createGateway(): PaymentGateway;

  processPayment(amount: number): boolean {
    const gateway = this.createGateway();
    return gateway.charge(amount);
  }
}

class StripeProcessor extends PaymentProcessor {
  createGateway(): PaymentGateway {
    return new StripeGateway();
  }
}

class PayPalProcessor extends PaymentProcessor {
  createGateway(): PaymentGateway {
    return new PayPalGateway();
  }
}

Builder Pattern

Builder Pattern

Fluent Builder

class QueryBuilder {
  private table: string = '';
  private conditions: string[] = [];
  private orderBy: string = '';
  private limitCount: number = 0;

  from(table: string): this {
    this.table = table;
    return this;
  }

  where(condition: string): this {
    this.conditions.push(condition);
    return this;
  }

  order(column: string): this {
    this.orderBy = column;
    return this;
  }

  limit(n: number): this {
    this.limitCount = n;
    return this;
  }

  build(): string {
    let query = `SELECT * FROM ${this.table}`;
    if (this.conditions.length) {
      query += ` WHERE ${this.conditions.join(' AND ')}`;
    }
    if (this.orderBy) {
      query += ` ORDER BY ${this.orderBy}`;
    }
    if (this.limitCount) {
      query += ` LIMIT ${this.limitCount}`;
    }
    return query;
  }
}

// Usage
const query = new QueryBuilder()
  .from('users')
  .where('age > 18')
  .where('active = true')
  .order('name')
  .limit(10)
  .build();

Type-Safe Builder

class ConfigBuilder {
  private config: Record<string, unknown> = {};

  set<K extends string, V>(key: K, value: V): ConfigBuilder {
    this.config[key] = value;
    return this;
  }

  build<T>(): T {
    return this.config as T;
  }
}

const config = new ConfigBuilder()
  .set('host', 'localhost')
  .set('port', 3000)
  .set('debug', true)
  .build<{ host: string; port: number; boolean: boolean }>();

Prototype Pattern

Prototype Pattern

Object Cloning

interface Prototype<T> {
  clone(): T;
}

class UserConfig implements Prototype<UserConfig> {
  constructor(
    public theme: string,
    public language: string,
    public notifications: boolean,
    public permissions: string[]
  ) {}

  clone(): UserConfig {
    // Deep clone permissions array
    return new UserConfig(
      this.theme,
      this.language,
      this.notifications,
      [...this.permissions]
    );
  }
}

// Usage
const defaultConfig = new UserConfig('dark', 'en', true, ['read', 'write']);
const adminConfig = defaultConfig.clone();
adminConfig.permissions.push('delete');
// original.permissions unchanged

Spread Operator Cloning

interface UserProfile {
  name: string;
  settings: {
    theme: string;
    notifications: boolean;
  };
}

function deepClone<T>(obj: T): T {
  return structuredClone(obj);
}

const original: UserProfile = {
  name: 'Alice',
  settings: { theme: 'dark', notifications: true }
};

const clone = deepClone(original);
clone.settings.theme = 'light';
// original.settings.theme still 'dark'