+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 50 of 354

๐ŸŽญ Anonymous Classes: Inline Class Definitions

Master anonymous classes in TypeScript to create inline, one-time-use classes for flexible and concise code patterns ๐Ÿš€

๐Ÿ’ŽAdvanced
25 min read

Prerequisites

  • Understanding of class expressions ๐Ÿ“
  • Closures and scope knowledge ๐Ÿ”
  • Interface and type basics ๐Ÿ’ป

What you'll learn

  • Create anonymous classes inline ๐ŸŽฏ
  • Understand use cases for anonymous classes ๐Ÿ—๏ธ
  • Implement factory patterns with anonymous classes ๐Ÿ›ก๏ธ
  • Master closure patterns in class contexts โœจ

๐ŸŽฏ Introduction

Welcome to the mysterious world of anonymous classes! ๐ŸŽ‰ In this guide, weโ€™ll explore how TypeScript allows you to create classes without names, directly at the point where theyโ€™re needed - perfect for one-time-use scenarios.

Youโ€™ll discover how anonymous classes are like masked performers ๐ŸŽญ - they appear, do their job brilliantly, and disappear without leaving a trace! Whether youโ€™re creating specialized implementations ๐ŸŽฏ, building adapter patterns ๐Ÿ”Œ, or designing callback handlers ๐Ÿ“ž, understanding anonymous classes adds elegance to your code.

By the end of this tutorial, youโ€™ll be confidently creating inline classes that capture context and provide exactly what you need, exactly where you need it! Letโ€™s unmask these powerful patterns! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Anonymous Classes

๐Ÿค” What are Anonymous Classes?

Anonymous classes are class expressions without a name, created inline at the point of use. Theyโ€™re perfect when you need a class just once and donโ€™t want to pollute your namespace with a named class definition.

Think of anonymous classes like:

  • ๐ŸŽญ Stand-in actors: Filling a role without needing credits
  • ๐ŸŽช Pop-up shops: Temporary but fully functional
  • ๐ŸŽจ Sketch vs painting: Quick implementation vs formal definition
  • ๐ŸŒŸ Shooting stars: Brief but impactful appearances

๐Ÿ’ก When to Use Anonymous Classes

Perfect scenarios for anonymous classes:

  1. Single-use implementations ๐ŸŽฏ: Need a class just once
  2. Callback handlers ๐Ÿ“ž: Event-specific implementations
  3. Test doubles ๐Ÿงช: Quick mocks and stubs
  4. Adapter patterns ๐Ÿ”Œ: Inline interface implementations

Real-world example: Event handlers ๐ŸŽฎ - Creating a specialized class just for handling a specific button click!

๐Ÿ”ง Basic Anonymous Classes

๐Ÿ“ Simple Anonymous Class Patterns

Letโ€™s start with fundamental patterns:

// ๐ŸŽฏ Basic anonymous class
const myObject = new (class {
  private value: number = 0;
  
  increment(): number {
    return ++this.value;
  }
  
  decrement(): number {
    return --this.value;
  }
  
  getValue(): number {
    return this.value;
  }
})();

console.log(myObject.increment()); // 1
console.log(myObject.increment()); // 2
console.log(myObject.getValue()); // 2

// ๐Ÿญ Factory returning anonymous class instance
const createCounter = (initial: number = 0) => {
  return new (class {
    private count = initial;
    
    next(): number {
      return ++this.count;
    }
    
    reset(): void {
      this.count = initial;
    }
    
    toString(): string {
      return `Counter(${this.count})`;
    }
  })();
};

const counter1 = createCounter(10);
const counter2 = createCounter(100);

console.log(counter1.next()); // 11
console.log(counter2.next()); // 101
console.log(counter1.toString()); // "Counter(11)"

// ๐ŸŽญ Anonymous class extending base class
abstract class Animal {
  abstract makeSound(): string;
  
  describe(): string {
    return `This animal says: ${this.makeSound()}`;
  }
}

const dog = new (class extends Animal {
  makeSound(): string {
    return 'Woof!';
  }
  
  wagTail(): void {
    console.log('*wagging tail happily*');
  }
})();

console.log(dog.describe()); // "This animal says: Woof!"
dog.wagTail(); // "*wagging tail happily*"

๐Ÿ”Œ Anonymous Classes Implementing Interfaces

Using anonymous classes to implement interfaces inline:

// ๐ŸŽฏ Interface implementations
interface Logger {
  log(message: string): void;
  error(message: string): void;
  warn(message: string): void;
}

interface LoggerConfig {
  prefix?: string;
  timestamp?: boolean;
  colors?: boolean;
}

// ๐Ÿ—๏ธ Create logger with anonymous class
const createLogger = (config: LoggerConfig = {}): Logger => {
  return new (class implements Logger {
    private format(level: string, message: string): string {
      let output = '';
      
      if (config.timestamp) {
        output += `[${new Date().toISOString()}] `;
      }
      
      if (config.prefix) {
        output += `${config.prefix} `;
      }
      
      output += `${level}: ${message}`;
      
      return output;
    }
    
    log(message: string): void {
      const formatted = this.format('LOG', message);
      if (config.colors) {
        console.log(`\x1b[37m${formatted}\x1b[0m`); // White
      } else {
        console.log(formatted);
      }
    }
    
    error(message: string): void {
      const formatted = this.format('ERROR', message);
      if (config.colors) {
        console.error(`\x1b[31m${formatted}\x1b[0m`); // Red
      } else {
        console.error(formatted);
      }
    }
    
    warn(message: string): void {
      const formatted = this.format('WARN', message);
      if (config.colors) {
        console.warn(`\x1b[33m${formatted}\x1b[0m`); // Yellow
      } else {
        console.warn(formatted);
      }
    }
  })();
};

// ๐Ÿ’ซ Different logger configurations
const simpleLogger = createLogger();
const fancyLogger = createLogger({
  prefix: '[MyApp]',
  timestamp: true,
  colors: true
});

simpleLogger.log('Hello world');
fancyLogger.error('Something went wrong!');

๐Ÿš€ Advanced Anonymous Class Patterns

๐ŸŽจ Closure-Based Anonymous Classes

Leveraging closures for private state:

// ๐Ÿ” State machine with anonymous class
type State = 'idle' | 'loading' | 'success' | 'error';
type StateHandler = () => void;

const createStateMachine = (initialState: State) => {
  // Private state via closure
  let currentState = initialState;
  const handlers = new Map<State, StateHandler[]>();
  const history: State[] = [initialState];
  
  return new (class {
    getState(): State {
      return currentState;
    }
    
    setState(newState: State): void {
      if (currentState !== newState) {
        history.push(newState);
        currentState = newState;
        this.notifyHandlers();
      }
    }
    
    onStateChange(state: State, handler: StateHandler): () => void {
      if (!handlers.has(state)) {
        handlers.set(state, []);
      }
      
      handlers.get(state)!.push(handler);
      
      // Return unsubscribe function
      return () => {
        const stateHandlers = handlers.get(state);
        if (stateHandlers) {
          const index = stateHandlers.indexOf(handler);
          if (index > -1) {
            stateHandlers.splice(index, 1);
          }
        }
      };
    }
    
    private notifyHandlers(): void {
      const stateHandlers = handlers.get(currentState);
      if (stateHandlers) {
        stateHandlers.forEach(handler => handler());
      }
    }
    
    getHistory(): State[] {
      return [...history];
    }
    
    canTransition(from: State, to: State): boolean {
      const validTransitions: Record<State, State[]> = {
        idle: ['loading'],
        loading: ['success', 'error'],
        success: ['idle', 'loading'],
        error: ['idle', 'loading']
      };
      
      return validTransitions[from]?.includes(to) ?? false;
    }
    
    transition(to: State): boolean {
      if (this.canTransition(currentState, to)) {
        this.setState(to);
        return true;
      }
      return false;
    }
  })();
};

// ๐Ÿ’ซ Usage
const machine = createStateMachine('idle');

machine.onStateChange('loading', () => {
  console.log('๐Ÿ”„ Loading started...');
});

machine.onStateChange('success', () => {
  console.log('โœ… Operation successful!');
});

machine.onStateChange('error', () => {
  console.log('โŒ Operation failed!');
});

console.log(machine.transition('loading')); // true - "๐Ÿ”„ Loading started..."
console.log(machine.transition('success')); // true - "โœ… Operation successful!"
console.log(machine.getHistory()); // ['idle', 'loading', 'success']

๐Ÿ—๏ธ Builder Pattern with Anonymous Classes

Creating fluent builders inline:

// ๐ŸŽฏ Generic builder interface
interface Builder<T> {
  build(): T;
}

// ๐Ÿ  House building example
interface House {
  floors: number;
  rooms: number;
  garage: boolean;
  pool: boolean;
  garden: boolean;
  style: 'modern' | 'traditional' | 'minimalist';
}

const createHouseBuilder = () => {
  // Private state
  const house: Partial<House> = {
    floors: 1,
    rooms: 1,
    garage: false,
    pool: false,
    garden: false,
    style: 'traditional'
  };
  
  return new (class implements Builder<House> {
    withFloors(floors: number) {
      house.floors = floors;
      return this;
    }
    
    withRooms(rooms: number) {
      house.rooms = rooms;
      return this;
    }
    
    withGarage() {
      house.garage = true;
      return this;
    }
    
    withPool() {
      house.pool = true;
      return this;
    }
    
    withGarden() {
      house.garden = true;
      return this;
    }
    
    withStyle(style: House['style']) {
      house.style = style;
      return this;
    }
    
    build(): House {
      // Validation
      if (house.rooms! < house.floors!) {
        throw new Error('Must have at least one room per floor');
      }
      
      if (house.pool && house.floors! > 3) {
        throw new Error('Pool not recommended for tall buildings');
      }
      
      return { ...house } as House;
    }
  })();
};

// ๐Ÿ’ซ Fluent API usage
const mansion = createHouseBuilder()
  .withFloors(3)
  .withRooms(10)
  .withGarage()
  .withPool()
  .withGarden()
  .withStyle('modern')
  .build();

console.log(mansion);
// { floors: 3, rooms: 10, garage: true, pool: true, garden: true, style: 'modern' }

const cottage = createHouseBuilder()
  .withFloors(1)
  .withRooms(3)
  .withGarden()
  .withStyle('traditional')
  .build();

console.log(cottage);
// { floors: 1, rooms: 3, garage: false, pool: false, garden: true, style: 'traditional' }

๐ŸŽช Event System with Anonymous Classes

๐Ÿ“ก Type-Safe Event Emitters

Building event systems with anonymous classes:

// ๐ŸŽฏ Typed event system
type EventMap = Record<string, any[]>;

interface Emitter<T extends EventMap> {
  on<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void;
  off<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void;
  emit<K extends keyof T>(event: K, ...args: T[K]): void;
  once<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void;
}

// ๐Ÿ—๏ธ Create typed event emitter
const createEmitter = <T extends EventMap>(): Emitter<T> => {
  const events = new Map<keyof T, Set<Function>>();
  
  return new (class implements Emitter<T> {
    on<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void {
      if (!events.has(event)) {
        events.set(event, new Set());
      }
      events.get(event)!.add(handler);
    }
    
    off<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void {
      events.get(event)?.delete(handler);
    }
    
    emit<K extends keyof T>(event: K, ...args: T[K]): void {
      events.get(event)?.forEach(handler => {
        handler(...args);
      });
    }
    
    once<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void {
      const wrappedHandler = (...args: T[K]) => {
        handler(...args);
        this.off(event, wrappedHandler);
      };
      this.on(event, wrappedHandler);
    }
  })();
};

// ๐Ÿ’ซ Type-safe usage
interface GameEvents {
  start: [];
  pause: [];
  resume: [];
  score: [points: number, player: string];
  gameOver: [winner: string, finalScore: number];
  powerUp: [type: 'speed' | 'shield' | 'damage', duration: number];
}

const gameEmitter = createEmitter<GameEvents>();

gameEmitter.on('score', (points, player) => {
  console.log(`${player} scored ${points} points!`);
});

gameEmitter.on('powerUp', (type, duration) => {
  console.log(`Power-up activated: ${type} for ${duration} seconds`);
});

gameEmitter.once('gameOver', (winner, score) => {
  console.log(`๐Ÿ† Game Over! ${winner} wins with ${score} points!`);
});

// Emit events
gameEmitter.emit('start');
gameEmitter.emit('score', 100, 'Player1');
gameEmitter.emit('powerUp', 'speed', 10);
gameEmitter.emit('gameOver', 'Player1', 500);

๐Ÿ”„ Observable Pattern with Anonymous Classes

Creating reactive data structures:

// ๐ŸŽฏ Observable value wrapper
interface Observer<T> {
  next(value: T): void;
  error?(error: Error): void;
  complete?(): void;
}

interface Observable<T> {
  subscribe(observer: Observer<T>): () => void;
  getValue(): T;
}

const createObservable = <T>(initialValue: T): Observable<T> & { setValue(value: T): void } => {
  let currentValue = initialValue;
  const observers = new Set<Observer<T>>();
  
  return new (class {
    getValue(): T {
      return currentValue;
    }
    
    setValue(value: T): void {
      currentValue = value;
      this.notify(value);
    }
    
    subscribe(observer: Observer<T>): () => void {
      observers.add(observer);
      observer.next(currentValue); // Emit current value immediately
      
      return () => {
        observers.delete(observer);
      };
    }
    
    private notify(value: T): void {
      observers.forEach(observer => {
        try {
          observer.next(value);
        } catch (error) {
          observer.error?.(error as Error);
        }
      });
    }
  })();
};

// ๐Ÿ—๏ธ Computed observables
const createComputed = <T, R>(
  source: Observable<T>,
  transform: (value: T) => R
): Observable<R> => {
  let cachedValue = transform(source.getValue());
  const observers = new Set<Observer<R>>();
  
  // Subscribe to source
  source.subscribe({
    next(value: T) {
      cachedValue = transform(value);
      observers.forEach(observer => observer.next(cachedValue));
    }
  });
  
  return new (class {
    getValue(): R {
      return cachedValue;
    }
    
    subscribe(observer: Observer<R>): () => void {
      observers.add(observer);
      observer.next(cachedValue);
      
      return () => {
        observers.delete(observer);
      };
    }
  })();
};

// ๐Ÿ’ซ Reactive system
const temperature = createObservable(20); // Celsius
const fahrenheit = createComputed(temperature, c => (c * 9/5) + 32);
const status = createComputed(temperature, t => 
  t < 0 ? 'freezing' : t < 10 ? 'cold' : t < 25 ? 'comfortable' : 'hot'
);

// Subscribe to changes
fahrenheit.subscribe({
  next(value) {
    console.log(`Temperature: ${value}ยฐF`);
  }
});

status.subscribe({
  next(value) {
    console.log(`Status: ${value}`);
  }
});

// Update temperature
temperature.setValue(30); // "Temperature: 86ยฐF", "Status: hot"
temperature.setValue(-5); // "Temperature: 23ยฐF", "Status: freezing"

๐ŸŽญ Testing with Anonymous Classes

๐Ÿงช Mock Objects and Test Doubles

Creating test doubles inline:

// ๐ŸŽฏ Testing interfaces
interface UserRepository {
  findById(id: string): Promise<User | null>;
  save(user: User): Promise<void>;
  delete(id: string): Promise<boolean>;
}

interface User {
  id: string;
  name: string;
  email: string;
}

interface EmailService {
  sendWelcomeEmail(user: User): Promise<void>;
  sendPasswordReset(email: string): Promise<void>;
}

// ๐Ÿ—๏ธ Service under test
class UserService {
  constructor(
    private repo: UserRepository,
    private emailService: EmailService
  ) {}
  
  async createUser(name: string, email: string): Promise<User> {
    const user: User = {
      id: Date.now().toString(),
      name,
      email
    };
    
    await this.repo.save(user);
    await this.emailService.sendWelcomeEmail(user);
    
    return user;
  }
  
  async deleteUser(id: string): Promise<boolean> {
    const user = await this.repo.findById(id);
    if (!user) return false;
    
    return this.repo.delete(id);
  }
}

// ๐Ÿงช Test with anonymous mocks
const testUserService = () => {
  // Track calls for assertions
  const calls = {
    save: [] as User[],
    sendWelcomeEmail: [] as User[],
    findById: [] as string[],
    delete: [] as string[]
  };
  
  // Create mocks with anonymous classes
  const mockRepo = new (class implements UserRepository {
    async findById(id: string): Promise<User | null> {
      calls.findById.push(id);
      return id === '123' 
        ? { id: '123', name: 'Test User', email: '[email protected]' }
        : null;
    }
    
    async save(user: User): Promise<void> {
      calls.save.push(user);
    }
    
    async delete(id: string): Promise<boolean> {
      calls.delete.push(id);
      return id === '123';
    }
  })();
  
  const mockEmailService = new (class implements EmailService {
    async sendWelcomeEmail(user: User): Promise<void> {
      calls.sendWelcomeEmail.push(user);
    }
    
    async sendPasswordReset(email: string): Promise<void> {
      // Not used in this test
    }
  })();
  
  // Run tests
  const service = new UserService(mockRepo, mockEmailService);
  
  // Test create user
  service.createUser('Alice', '[email protected]').then(user => {
    console.assert(calls.save.length === 1, 'Save should be called once');
    console.assert(calls.sendWelcomeEmail.length === 1, 'Email should be sent');
    console.assert(user.name === 'Alice', 'User name should match');
    console.log('โœ… Create user test passed');
  });
  
  // Test delete user
  service.deleteUser('123').then(result => {
    console.assert(result === true, 'Should delete existing user');
    console.assert(calls.findById.includes('123'), 'Should look up user');
    console.assert(calls.delete.includes('123'), 'Should call delete');
    console.log('โœ… Delete user test passed');
  });
  
  service.deleteUser('999').then(result => {
    console.assert(result === false, 'Should not delete non-existent user');
    console.log('โœ… Delete non-existent user test passed');
  });
};

testUserService();

๐Ÿ›ก๏ธ Adapter Pattern with Anonymous Classes

๐Ÿ”Œ Creating Adapters Inline

Adapting interfaces on the fly:

// ๐ŸŽฏ Legacy and modern interfaces
interface LegacyPaymentGateway {
  makePayment(amount: number, currency: string, cardNumber: string): string;
  checkStatus(transactionId: string): 'pending' | 'completed' | 'failed';
}

interface ModernPaymentGateway {
  charge(params: {
    amount: number;
    currency: string;
    source: string;
    metadata?: Record<string, any>;
  }): Promise<{ id: string; status: string }>;
  
  getTransaction(id: string): Promise<{
    id: string;
    status: 'pending' | 'succeeded' | 'failed';
    amount: number;
  }>;
}

// ๐Ÿ—๏ธ Adapt legacy to modern interface
const adaptLegacyGateway = (legacy: LegacyPaymentGateway): ModernPaymentGateway => {
  return new (class implements ModernPaymentGateway {
    async charge(params: {
      amount: number;
      currency: string;
      source: string;
      metadata?: Record<string, any>;
    }): Promise<{ id: string; status: string }> {
      // Adapt the call
      const transactionId = legacy.makePayment(
        params.amount,
        params.currency,
        params.source
      );
      
      // Simulate async behavior
      await new Promise(resolve => setTimeout(resolve, 100));
      
      const legacyStatus = legacy.checkStatus(transactionId);
      const modernStatus = this.convertStatus(legacyStatus);
      
      return { id: transactionId, status: modernStatus };
    }
    
    async getTransaction(id: string): Promise<{
      id: string;
      status: 'pending' | 'succeeded' | 'failed';
      amount: number;
    }> {
      const legacyStatus = legacy.checkStatus(id);
      
      return {
        id,
        status: legacyStatus === 'completed' ? 'succeeded' : legacyStatus === 'failed' ? 'failed' : 'pending',
        amount: 0 // Legacy doesn't provide this
      };
    }
    
    private convertStatus(legacyStatus: string): string {
      switch (legacyStatus) {
        case 'completed': return 'succeeded';
        case 'failed': return 'failed';
        default: return 'pending';
      }
    }
  })();
};

// ๐Ÿ’ซ Usage
const legacyGateway: LegacyPaymentGateway = {
  makePayment(amount, currency, cardNumber) {
    console.log(`Legacy payment: ${amount} ${currency}`);
    return `TXN_${Date.now()}`;
  },
  checkStatus(transactionId) {
    // Simulate random status
    const statuses: ('pending' | 'completed' | 'failed')[] = ['pending', 'completed', 'failed'];
    return statuses[Math.floor(Math.random() * statuses.length)];
  }
};

const modernGateway = adaptLegacyGateway(legacyGateway);

// Use modern interface
modernGateway.charge({
  amount: 100,
  currency: 'USD',
  source: '4242424242424242',
  metadata: { orderId: '12345' }
}).then(result => {
  console.log('Payment result:', result);
});

๐ŸŽฎ Hands-On Exercise

Letโ€™s build a plugin system using anonymous classes!

๐Ÿ“ Challenge: Dynamic Plugin System

Create a plugin system that:

  1. Allows plugins to be defined inline as anonymous classes
  2. Supports plugin dependencies
  3. Provides lifecycle hooks
  4. Maintains type safety
// Your challenge: Implement this plugin system
interface Plugin {
  name: string;
  version: string;
  dependencies?: string[];
  install(app: Application): void;
  uninstall?(app: Application): void;
}

interface Application {
  plugins: Map<string, Plugin>;
  state: Map<string, any>;
  
  use(plugin: Plugin): this;
  has(pluginName: string): boolean;
  get<T>(key: string): T | undefined;
  set(key: string, value: any): void;
}

// Example usage to implement:
const app = createApplication();

// Define plugins inline
app.use(new (class implements Plugin {
  name = 'logger';
  version = '1.0.0';
  
  install(app: Application): void {
    app.set('log', (message: string) => {
      console.log(`[${new Date().toISOString()}] ${message}`);
    });
  }
})());

app.use(new (class implements Plugin {
  name = 'router';
  version = '1.0.0';
  dependencies = ['logger'];
  
  install(app: Application): void {
    const log = app.get<(msg: string) => void>('log');
    const routes = new Map<string, Function>();
    
    app.set('route', (path: string, handler: Function) => {
      routes.set(path, handler);
      log?.(`Route registered: ${path}`);
    });
    
    app.set('navigate', (path: string) => {
      const handler = routes.get(path);
      if (handler) {
        log?.(`Navigating to: ${path}`);
        handler();
      }
    });
  }
})());

// Implement the createApplication function!

๐Ÿ’ก Solution

Click to see the solution
const createApplication = (): Application => {
  const plugins = new Map<string, Plugin>();
  const state = new Map<string, any>();
  const loadOrder: string[] = [];
  
  return new (class implements Application {
    plugins = plugins;
    state = state;
    
    use(plugin: Plugin): this {
      // Check if already installed
      if (this.has(plugin.name)) {
        console.warn(`Plugin ${plugin.name} is already installed`);
        return this;
      }
      
      // Check dependencies
      if (plugin.dependencies) {
        for (const dep of plugin.dependencies) {
          if (!this.has(dep)) {
            throw new Error(`Plugin ${plugin.name} requires ${dep} to be installed first`);
          }
        }
      }
      
      // Install plugin
      console.log(`Installing plugin: ${plugin.name} v${plugin.version}`);
      plugin.install(this);
      plugins.set(plugin.name, plugin);
      loadOrder.push(plugin.name);
      
      return this;
    }
    
    has(pluginName: string): boolean {
      return plugins.has(pluginName);
    }
    
    get<T>(key: string): T | undefined {
      return state.get(key) as T;
    }
    
    set(key: string, value: any): void {
      state.set(key, value);
    }
    
    // Additional methods
    uninstall(pluginName: string): boolean {
      const plugin = plugins.get(pluginName);
      if (!plugin) return false;
      
      // Check if other plugins depend on this
      const dependents = Array.from(plugins.values()).filter(p => 
        p.dependencies?.includes(pluginName)
      );
      
      if (dependents.length > 0) {
        throw new Error(`Cannot uninstall ${pluginName}: required by ${dependents.map(p => p.name).join(', ')}`);
      }
      
      // Uninstall
      plugin.uninstall?.(this);
      plugins.delete(pluginName);
      
      // Remove from load order
      const index = loadOrder.indexOf(pluginName);
      if (index > -1) {
        loadOrder.splice(index, 1);
      }
      
      return true;
    }
    
    getLoadOrder(): string[] {
      return [...loadOrder];
    }
    
    reset(): void {
      // Uninstall in reverse order
      [...loadOrder].reverse().forEach(name => {
        const plugin = plugins.get(name);
        plugin?.uninstall?.(this);
      });
      
      plugins.clear();
      state.clear();
      loadOrder.length = 0;
    }
  })();
};

// ๐ŸŽฎ Extended example with more plugins
const app = createApplication();

// Logger plugin
app.use(new (class implements Plugin {
  name = 'logger';
  version = '1.0.0';
  
  install(app: Application): void {
    const logs: string[] = [];
    
    app.set('log', (message: string) => {
      const entry = `[${new Date().toISOString()}] ${message}`;
      logs.push(entry);
      console.log(entry);
    });
    
    app.set('getLogs', () => [...logs]);
    app.set('clearLogs', () => logs.length = 0);
  }
  
  uninstall(app: Application): void {
    app.set('log', undefined);
    app.set('getLogs', undefined);
    app.set('clearLogs', undefined);
  }
})());

// Auth plugin
app.use(new (class implements Plugin {
  name = 'auth';
  version = '1.0.0';
  dependencies = ['logger'];
  
  install(app: Application): void {
    const log = app.get<(msg: string) => void>('log')!;
    let currentUser: { id: string; name: string } | null = null;
    
    app.set('login', (id: string, name: string) => {
      currentUser = { id, name };
      log(`User logged in: ${name}`);
    });
    
    app.set('logout', () => {
      if (currentUser) {
        log(`User logged out: ${currentUser.name}`);
        currentUser = null;
      }
    });
    
    app.set('getCurrentUser', () => currentUser);
    app.set('isAuthenticated', () => currentUser !== null);
  }
})());

// API plugin
app.use(new (class implements Plugin {
  name = 'api';
  version = '1.0.0';
  dependencies = ['logger', 'auth'];
  
  install(app: Application): void {
    const log = app.get<(msg: string) => void>('log')!;
    const isAuthenticated = app.get<() => boolean>('isAuthenticated')!;
    
    app.set('apiRequest', async (endpoint: string, options?: RequestInit) => {
      if (!isAuthenticated()) {
        throw new Error('Must be authenticated to make API requests');
      }
      
      log(`API request: ${options?.method || 'GET'} ${endpoint}`);
      
      // Simulate API call
      return { success: true, data: { endpoint } };
    });
  }
})());

// Use the plugins
const log = app.get<(msg: string) => void>('log')!;
const login = app.get<(id: string, name: string) => void>('login')!;
const apiRequest = app.get<(endpoint: string) => Promise<any>>('apiRequest')!;

log('Application started');
login('123', 'Alice');

apiRequest('/users').then(result => {
  log(`API response: ${JSON.stringify(result)}`);
});

console.log('Load order:', app.getLoadOrder());
// ['logger', 'auth', 'api']

๐ŸŽฏ Summary

Youโ€™ve mastered anonymous classes in TypeScript! ๐ŸŽ‰ You learned how to:

  • ๐ŸŽญ Create inline class definitions without names
  • ๐Ÿ” Leverage closures for private state
  • ๐Ÿ—๏ธ Build factory patterns with anonymous classes
  • ๐Ÿงช Create test doubles and mocks inline
  • ๐Ÿ”Œ Implement adapter patterns on the fly
  • โœจ Design flexible plugin systems

Anonymous classes provide elegant solutions for one-time-use scenarios, keeping your code clean and focused without namespace pollution!