+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 49 of 354

๐ŸŽจ Class Expressions: Dynamic Class Creation

Master class expressions in TypeScript to create classes dynamically at runtime and build flexible, reusable patterns ๐Ÿš€

๐Ÿ’ŽAdvanced
25 min read

Prerequisites

  • Understanding of classes in TypeScript ๐Ÿ“
  • Function expressions knowledge ๐Ÿ”
  • Generic types understanding ๐Ÿ’ป

What you'll learn

  • Create classes dynamically with expressions ๐ŸŽฏ
  • Build factory functions that return classes ๐Ÿ—๏ธ
  • Implement conditional class creation ๐Ÿ›ก๏ธ
  • Design flexible class hierarchies โœจ

๐ŸŽฏ Introduction

Welcome to the dynamic world of class expressions! ๐ŸŽ‰ In this guide, weโ€™ll explore how TypeScript allows you to create classes as expressions rather than declarations, enabling powerful runtime class creation patterns.

Youโ€™ll discover how class expressions are like artistโ€™s palettes ๐ŸŽจ - you can mix and create new classes on the fly! Whether youโ€™re building factory patterns ๐Ÿญ, creating conditional classes ๐Ÿ”€, or designing plugin systems ๐Ÿ”Œ, understanding class expressions opens up flexible architectural possibilities.

By the end of this tutorial, youโ€™ll be confidently creating classes dynamically and building sophisticated class factories! Letโ€™s paint with classes! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Class Expressions

๐Ÿค” What are Class Expressions?

Class expressions are a way to define classes as values rather than declarations. Just like function expressions, they can be assigned to variables, passed as arguments, or returned from functions - giving you runtime control over class creation!

Think of class expressions like:

  • ๐ŸŽจ Paint mixing: Creating new colors (classes) on demand
  • ๐Ÿญ Factory assembly: Building products (classes) to specification
  • ๐Ÿงฌ DNA splicing: Combining traits dynamically
  • ๐ŸŽญ Costume changes: Different appearances for different scenes

๐Ÿ’ก Class Declaration vs Expression

Hereโ€™s the key difference:

// ๐Ÿ“ Class Declaration (hoisted, named)
class MyClass {
  // Class body
}

// ๐ŸŽจ Class Expression (not hoisted, can be anonymous)
const MyClass = class {
  // Class body
};

// ๐Ÿท๏ธ Named Class Expression
const MyClass = class InternalName {
  // Can reference InternalName inside class
};

Real-world example: React components ๐ŸŽฏ - Higher-order components often use class expressions to create wrapper classes dynamically!

๐Ÿ”ง Basic Class Expressions

๐Ÿ“ Anonymous Class Expressions

Letโ€™s start with the fundamentals:

// ๐ŸŽฏ Simple anonymous class expression
const Animal = class {
  constructor(public name: string) {}
  
  speak(): string {
    return `${this.name} makes a sound`;
  }
};

const dog = new Animal('Buddy');
console.log(dog.speak()); // "Buddy makes a sound"

// ๐Ÿญ Factory function returning class
const createClass = (baseValue: number) => {
  return class {
    value = baseValue;
    
    increment(): number {
      return ++this.value;
    }
    
    decrement(): number {
      return --this.value;
    }
  };
};

const CounterClass = createClass(10);
const counter = new CounterClass();
console.log(counter.increment()); // 11
console.log(counter.value); // 11

// ๐ŸŽจ Conditional class creation
const createLogger = (useConsole: boolean) => {
  if (useConsole) {
    return class ConsoleLogger {
      log(message: string): void {
        console.log(`[Console]: ${message}`);
      }
    };
  } else {
    return class FileLogger {
      private logs: string[] = [];
      
      log(message: string): void {
        this.logs.push(`[File]: ${message}`);
      }
      
      getLogs(): string[] {
        return this.logs;
      }
    };
  }
};

const Logger = createLogger(false);
const logger = new Logger();
logger.log('Hello');
// Type-safe access to FileLogger methods
if ('getLogs' in logger) {
  console.log(logger.getLogs());
}

๐Ÿท๏ธ Named Class Expressions

Named expressions provide internal references:

// ๐Ÿ”„ Self-referencing class expression
const Fibonacci = class Fib {
  static cache = new Map<number, number>();
  
  static calculate(n: number): number {
    if (n <= 1) return n;
    
    if (Fib.cache.has(n)) {
      return Fib.cache.get(n)!;
    }
    
    const result = Fib.calculate(n - 1) + Fib.calculate(n - 2);
    Fib.cache.set(n, result);
    return result;
  }
  
  static reset(): void {
    Fib.cache.clear();
  }
};

console.log(Fibonacci.calculate(10)); // 55
console.log(Fibonacci.cache.size); // 9 cached values

// ๐ŸŽญ Class with internal name for debugging
const Component = class MyComponent {
  static instances = 0;
  id: number;
  
  constructor() {
    this.id = ++MyComponent.instances;
    console.log(`Creating ${MyComponent.name} #${this.id}`);
  }
  
  destroy(): void {
    console.log(`Destroying ${MyComponent.name} #${this.id}`);
  }
};

const comp1 = new Component(); // "Creating MyComponent #1"
const comp2 = new Component(); // "Creating MyComponent #2"

๐Ÿš€ Advanced Patterns

๐Ÿญ Generic Class Factories

Creating flexible class factories with generics:

// ๐Ÿงฌ Generic class factory
const createRepository = <T extends { id: string }>() => {
  return class Repository {
    private items = new Map<string, T>();
    
    add(item: T): void {
      this.items.set(item.id, item);
    }
    
    get(id: string): T | undefined {
      return this.items.get(id);
    }
    
    update(id: string, updates: Partial<T>): T | undefined {
      const item = this.items.get(id);
      if (item) {
        const updated = { ...item, ...updates };
        this.items.set(id, updated);
        return updated;
      }
      return undefined;
    }
    
    delete(id: string): boolean {
      return this.items.delete(id);
    }
    
    findAll(): T[] {
      return Array.from(this.items.values());
    }
    
    findBy(predicate: (item: T) => boolean): T[] {
      return this.findAll().filter(predicate);
    }
  };
};

// ๐Ÿ’ผ Type-safe repositories
interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

interface Product {
  id: string;
  name: string;
  price: number;
  stock: number;
}

const UserRepository = createRepository<User>();
const ProductRepository = createRepository<Product>();

const userRepo = new UserRepository();
userRepo.add({ id: '1', name: 'Alice', email: '[email protected]', role: 'admin' });
userRepo.add({ id: '2', name: 'Bob', email: '[email protected]', role: 'user' });

const admins = userRepo.findBy(user => user.role === 'admin');
console.log(admins); // [{ id: '1', name: 'Alice', ... }]

const productRepo = new ProductRepository();
productRepo.add({ id: 'p1', name: 'Laptop', price: 999, stock: 10 });
productRepo.update('p1', { stock: 8 });

๐ŸŽจ Dynamic Class Extension

Creating classes that extend dynamically:

// ๐Ÿ”ง Mixin-style class factory
type Constructor<T = {}> = new (...args: any[]) => T;

const createEnhancedClass = <TBase extends Constructor>(Base: TBase) => {
  return class Enhanced extends Base {
    private _metadata = new Map<string, any>();
    private _observers = new Set<(key: string, value: any) => void>();
    
    setMetadata(key: string, value: any): void {
      this._metadata.set(key, value);
      this._notifyObservers(key, value);
    }
    
    getMetadata(key: string): any {
      return this._metadata.get(key);
    }
    
    observe(callback: (key: string, value: any) => void): () => void {
      this._observers.add(callback);
      return () => this._observers.delete(callback);
    }
    
    private _notifyObservers(key: string, value: any): void {
      this._observers.forEach(callback => callback(key, value));
    }
    
    toJSON(): any {
      const baseJSON = 'toJSON' in super.prototype
        ? (super as any).toJSON.call(this)
        : { ...this };
      
      return {
        ...baseJSON,
        _metadata: Object.fromEntries(this._metadata)
      };
    }
  };
};

// ๐Ÿ—๏ธ Base classes
class Person {
  constructor(public name: string, public age: number) {}
}

class Vehicle {
  constructor(public brand: string, public model: string) {}
  
  getInfo(): string {
    return `${this.brand} ${this.model}`;
  }
}

// ๐ŸŽฏ Enhanced versions
const EnhancedPerson = createEnhancedClass(Person);
const EnhancedVehicle = createEnhancedClass(Vehicle);

const person = new EnhancedPerson('Alice', 30);
person.setMetadata('department', 'Engineering');
person.setMetadata('level', 'Senior');

const unsubscribe = person.observe((key, value) => {
  console.log(`Metadata changed: ${key} = ${value}`);
});

person.setMetadata('level', 'Lead'); // "Metadata changed: level = Lead"

const vehicle = new EnhancedVehicle('Tesla', 'Model 3');
vehicle.setMetadata('color', 'red');
vehicle.setMetadata('autopilot', true);
console.log(vehicle.getInfo()); // "Tesla Model 3"
console.log(vehicle.toJSON()); // Includes metadata

๐ŸŽญ Conditional Class Creation

๐Ÿ”€ Runtime Class Selection

Creating different classes based on conditions:

// ๐ŸŽฏ Environment-based class creation
type Environment = 'development' | 'production' | 'test';

const createDatabase = (env: Environment) => {
  switch (env) {
    case 'development':
      return class DevelopmentDB {
        private data = new Map<string, any>();
        
        async connect(): Promise<void> {
          console.log('๐Ÿ“Š Connected to in-memory database');
        }
        
        async query(sql: string): Promise<any[]> {
          console.log(`๐Ÿ” Executing: ${sql}`);
          // Simulate query with in-memory data
          return Array.from(this.data.values());
        }
        
        async insert(table: string, data: any): Promise<void> {
          const id = Date.now().toString();
          this.data.set(id, { id, table, ...data });
          console.log(`โž• Inserted into ${table}:`, data);
        }
        
        reset(): void {
          this.data.clear();
          console.log('๐Ÿ”„ Database reset');
        }
      };
      
    case 'production':
      return class ProductionDB {
        private connection: any;
        
        async connect(): Promise<void> {
          // Real database connection
          console.log('๐Ÿ” Connected to production database');
        }
        
        async query(sql: string): Promise<any[]> {
          // Real query execution
          console.log(`โšก Executing optimized query`);
          return [];
        }
        
        async insert(table: string, data: any): Promise<void> {
          // Real insert with transactions
          console.log(`๐Ÿ’พ Inserting into ${table} with transaction`);
        }
        
        async backup(): Promise<void> {
          console.log('๐Ÿ’ผ Creating database backup');
        }
      };
      
    case 'test':
      return class TestDB {
        private mocks = new Map<string, any[]>();
        
        async connect(): Promise<void> {
          console.log('๐Ÿงช Connected to test database');
        }
        
        async query(sql: string): Promise<any[]> {
          // Return mocked data
          return this.mocks.get(sql) || [];
        }
        
        async insert(table: string, data: any): Promise<void> {
          // Store in mocks
          const existing = this.mocks.get(table) || [];
          this.mocks.set(table, [...existing, data]);
        }
        
        mock(key: string, data: any[]): void {
          this.mocks.set(key, data);
        }
        
        verify(): Map<string, any[]> {
          return new Map(this.mocks);
        }
      };
  }
};

// ๐Ÿ’ซ Usage based on environment
const Database = createDatabase(process.env.NODE_ENV as Environment || 'development');
const db = new Database();

await db.connect();
await db.insert('users', { name: 'Alice', email: '[email protected]' });

// Type-safe environment-specific methods
if ('reset' in db) {
  // Development environment
  db.reset();
} else if ('backup' in db) {
  // Production environment
  await db.backup();
} else if ('mock' in db) {
  // Test environment
  db.mock('SELECT * FROM users', [{ id: 1, name: 'Test User' }]);
}

๐Ÿ—๏ธ Feature-Based Class Composition

Building classes with optional features:

// ๐Ÿ”Œ Feature flags type
interface Features {
  logging?: boolean;
  caching?: boolean;
  validation?: boolean;
  metrics?: boolean;
}

// ๐ŸŽจ Dynamic API client factory
const createAPIClient = (features: Features = {}) => {
  // Base class
  let ClientClass = class {
    constructor(protected baseURL: string) {}
    
    protected async request(endpoint: string, options: RequestInit = {}): Promise<any> {
      const response = await fetch(`${this.baseURL}${endpoint}`, options);
      return response.json();
    }
  };
  
  // Add logging
  if (features.logging) {
    const BaseClass = ClientClass;
    ClientClass = class extends BaseClass {
      protected async request(endpoint: string, options: RequestInit = {}): Promise<any> {
        console.log(`๐Ÿ“ก API Request: ${options.method || 'GET'} ${endpoint}`);
        const start = Date.now();
        
        try {
          const result = await super.request(endpoint, options);
          console.log(`โœ… Response received in ${Date.now() - start}ms`);
          return result;
        } catch (error) {
          console.error(`โŒ Request failed:`, error);
          throw error;
        }
      }
    };
  }
  
  // Add caching
  if (features.caching) {
    const BaseClass = ClientClass;
    ClientClass = class extends BaseClass {
      private cache = new Map<string, { data: any; timestamp: number }>();
      private cacheTTL = 5 * 60 * 1000; // 5 minutes
      
      protected async request(endpoint: string, options: RequestInit = {}): Promise<any> {
        if (options.method === 'GET' || !options.method) {
          const cacheKey = endpoint;
          const cached = this.cache.get(cacheKey);
          
          if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
            console.log(`๐Ÿ’พ Cache hit: ${endpoint}`);
            return cached.data;
          }
        }
        
        const result = await super.request(endpoint, options);
        
        if (options.method === 'GET' || !options.method) {
          this.cache.set(endpoint, { data: result, timestamp: Date.now() });
        }
        
        return result;
      }
      
      clearCache(): void {
        this.cache.clear();
      }
    };
  }
  
  // Add validation
  if (features.validation) {
    const BaseClass = ClientClass;
    ClientClass = class extends BaseClass {
      private validators = new Map<string, (data: any) => boolean>();
      
      addValidator(endpoint: string, validator: (data: any) => boolean): void {
        this.validators.set(endpoint, validator);
      }
      
      protected async request(endpoint: string, options: RequestInit = {}): Promise<any> {
        const result = await super.request(endpoint, options);
        
        const validator = this.validators.get(endpoint);
        if (validator && !validator(result)) {
          throw new Error(`Validation failed for ${endpoint}`);
        }
        
        return result;
      }
    };
  }
  
  // Add metrics
  if (features.metrics) {
    const BaseClass = ClientClass;
    ClientClass = class extends BaseClass {
      private metrics = {
        requests: 0,
        errors: 0,
        totalTime: 0,
        endpoints: new Map<string, number>()
      };
      
      protected async request(endpoint: string, options: RequestInit = {}): Promise<any> {
        this.metrics.requests++;
        const count = this.metrics.endpoints.get(endpoint) || 0;
        this.metrics.endpoints.set(endpoint, count + 1);
        
        const start = Date.now();
        
        try {
          const result = await super.request(endpoint, options);
          this.metrics.totalTime += Date.now() - start;
          return result;
        } catch (error) {
          this.metrics.errors++;
          throw error;
        }
      }
      
      getMetrics() {
        return {
          ...this.metrics,
          averageTime: this.metrics.totalTime / this.metrics.requests,
          errorRate: this.metrics.errors / this.metrics.requests,
          endpoints: Object.fromEntries(this.metrics.endpoints)
        };
      }
    };
  }
  
  return ClientClass;
};

// ๐Ÿ’ซ Create different API clients
const BasicClient = createAPIClient();
const basicAPI = new BasicClient('https://api.example.com');

const FullClient = createAPIClient({
  logging: true,
  caching: true,
  validation: true,
  metrics: true
});

const fullAPI = new FullClient('https://api.example.com');

// Type-safe feature access
if ('addValidator' in fullAPI) {
  fullAPI.addValidator('/users', (data) => Array.isArray(data));
}

if ('getMetrics' in fullAPI) {
  console.log(fullAPI.getMetrics());
}

๐ŸŽช Class Expression Patterns

๐Ÿ”„ Self-Modifying Classes

Classes that modify themselves:

// ๐Ÿงฌ Evolving class pattern
const createEvolvingClass = () => {
  let version = 1;
  
  return class EvolvingEntity {
    static version = version;
    private features: string[] = [];
    
    constructor(public name: string) {
      this.features.push(`v${EvolvingEntity.version}`);
    }
    
    static evolve(newFeature: string): typeof EvolvingEntity {
      version++;
      
      const CurrentClass = this;
      return class extends CurrentClass {
        static version = version;
        
        constructor(name: string) {
          super(name);
          this.features.push(newFeature);
        }
        
        [newFeature](): void {
          console.log(`${this.name} uses ${newFeature}!`);
        }
      } as any;
    }
    
    describe(): string {
      return `${this.name} has features: ${this.features.join(', ')}`;
    }
  };
};

// ๐ŸŽฎ Evolution in action
let Entity = createEvolvingClass();
const basic = new Entity('Basic');
console.log(basic.describe()); // "Basic has features: v1"

Entity = Entity.evolve('fly');
const flying = new Entity('Flyer');
console.log(flying.describe()); // "Flyer has features: v1, fly"
(flying as any).fly(); // "Flyer uses fly!"

Entity = Entity.evolve('swim');
const swimming = new Entity('Swimmer');
console.log(swimming.describe()); // "Swimmer has features: v1, fly, swim"
(swimming as any).fly(); // "Swimmer uses fly!"
(swimming as any).swim(); // "Swimmer uses swim!"

๐Ÿท๏ธ Tagged Class Templates

Creating specialized classes with metadata:

// ๐Ÿท๏ธ Class tagging system
interface ClassMetadata {
  name: string;
  version: string;
  author: string;
  tags: string[];
}

const createTaggedClass = <T extends object>(
  metadata: ClassMetadata,
  implementation: T
) => {
  const TaggedClass = class {
    static metadata = metadata;
    
    static hasTag(tag: string): boolean {
      return TaggedClass.metadata.tags.includes(tag);
    }
    
    static addTag(tag: string): void {
      if (!TaggedClass.metadata.tags.includes(tag)) {
        TaggedClass.metadata.tags.push(tag);
      }
    }
    
    static getInfo(): string {
      const { name, version, author, tags } = TaggedClass.metadata;
      return `${name} v${version} by ${author} [${tags.join(', ')}]`;
    }
  };
  
  // Merge implementation
  Object.assign(TaggedClass.prototype, implementation);
  
  return TaggedClass as typeof TaggedClass & (new () => T);
};

// ๐ŸŽฏ Create tagged classes
const HTTPClient = createTaggedClass(
  {
    name: 'HTTPClient',
    version: '2.0.0',
    author: 'DevTeam',
    tags: ['network', 'async', 'rest']
  },
  {
    async get(url: string) {
      return `GET ${url}`;
    },
    async post(url: string, data: any) {
      return `POST ${url}`;
    }
  }
);

console.log(HTTPClient.getInfo());
// "HTTPClient v2.0.0 by DevTeam [network, async, rest]"

HTTPClient.addTag('production-ready');
console.log(HTTPClient.hasTag('async')); // true

const client = new HTTPClient();
console.log(await client.get('/api/users')); // "GET /api/users"

๐Ÿ›ก๏ธ Type Safety with Class Expressions

๐Ÿ” Type Inference and Constraints

Ensuring type safety with dynamic classes:

// ๐ŸŽฏ Type-safe class factory with constraints
interface Plugin<T = any> {
  name: string;
  install(instance: T): void;
}

const createPluggableClass = <TPlugins extends Plugin[]>(
  name: string,
  plugins: TPlugins
) => {
  return class Pluggable {
    static className = name;
    private installedPlugins = new Set<string>();
    
    constructor() {
      // Install all plugins
      plugins.forEach(plugin => {
        plugin.install(this);
        this.installedPlugins.add(plugin.name);
      });
    }
    
    hasPlugin(name: string): boolean {
      return this.installedPlugins.has(name);
    }
    
    getPlugins(): string[] {
      return Array.from(this.installedPlugins);
    }
  };
};

// ๐Ÿ”Œ Define plugins with proper typing
const loggingPlugin: Plugin = {
  name: 'logging',
  install(instance: any) {
    instance.log = (message: string) => {
      console.log(`[${new Date().toISOString()}] ${message}`);
    };
  }
};

const validationPlugin: Plugin = {
  name: 'validation',
  install(instance: any) {
    instance.validate = (data: any, schema: any) => {
      // Validation logic
      return true;
    };
  }
};

// ๐Ÿ—๏ธ Create class with plugins
const MyService = createPluggableClass('MyService', [
  loggingPlugin,
  validationPlugin
]);

const service = new MyService();
console.log(service.hasPlugin('logging')); // true
console.log(service.getPlugins()); // ['logging', 'validation']

// Access plugin methods (with type casting for now)
(service as any).log('Service initialized');

๐ŸŽฎ Hands-On Exercise

Letโ€™s build a game entity system with dynamic class creation!

๐Ÿ“ Challenge: Dynamic Game Entity System

Create a system that:

  1. Dynamically creates entity classes based on components
  2. Supports runtime class composition
  3. Allows entity evolution during gameplay
  4. Maintains type safety
// Your challenge: Implement this system
interface Component {
  name: string;
  onAttach?(entity: any): void;
  onUpdate?(entity: any, deltaTime: number): void;
  onDetach?(entity: any): void;
}

interface EntityClass {
  new (id: string): Entity;
  addComponent(component: Component): EntityClass;
  removeComponent(componentName: string): EntityClass;
  getComponents(): string[];
}

interface Entity {
  id: string;
  update(deltaTime: number): void;
  hasComponent(name: string): boolean;
}

// Example components to implement
const HealthComponent: Component = {
  name: 'health',
  onAttach(entity) {
    entity.health = 100;
    entity.maxHealth = 100;
    entity.takeDamage = (amount: number) => {
      entity.health = Math.max(0, entity.health - amount);
    };
    entity.heal = (amount: number) => {
      entity.health = Math.min(entity.maxHealth, entity.health + amount);
    };
  }
};

const MovementComponent: Component = {
  name: 'movement',
  onAttach(entity) {
    entity.x = 0;
    entity.y = 0;
    entity.speed = 5;
    entity.move = (dx: number, dy: number) => {
      entity.x += dx * entity.speed;
      entity.y += dy * entity.speed;
    };
  },
  onUpdate(entity, deltaTime) {
    // Update position based on velocity
  }
};

// Implement the createEntityClass function!
const createEntityClass = (baseComponents: Component[] = []): EntityClass => {
  // Your implementation here
};

๐Ÿ’ก Solution

Click to see the solution
const createEntityClass = (baseComponents: Component[] = []): EntityClass => {
  const components = new Map<string, Component>(
    baseComponents.map(c => [c.name, c])
  );
  
  const EntityClass = class implements Entity {
    static components = components;
    id: string;
    
    constructor(id: string) {
      this.id = id;
      
      // Attach all components
      EntityClass.components.forEach(component => {
        component.onAttach?.(this);
      });
    }
    
    update(deltaTime: number): void {
      EntityClass.components.forEach(component => {
        component.onUpdate?.(this, deltaTime);
      });
    }
    
    hasComponent(name: string): boolean {
      return EntityClass.components.has(name);
    }
    
    static addComponent(component: Component): EntityClass {
      const newComponents = [...baseComponents, component];
      return createEntityClass(newComponents);
    }
    
    static removeComponent(componentName: string): EntityClass {
      const newComponents = baseComponents.filter(c => c.name !== componentName);
      return createEntityClass(newComponents);
    }
    
    static getComponents(): string[] {
      return Array.from(EntityClass.components.keys());
    }
  } as any as EntityClass;
  
  return EntityClass;
};

// ๐ŸŽฎ Advanced component system
const PhysicsComponent: Component = {
  name: 'physics',
  onAttach(entity) {
    entity.vx = 0;
    entity.vy = 0;
    entity.gravity = 9.8;
    entity.applyForce = (fx: number, fy: number) => {
      entity.vx += fx;
      entity.vy += fy;
    };
  },
  onUpdate(entity, deltaTime) {
    if (entity.hasComponent('movement')) {
      entity.vy += entity.gravity * deltaTime;
      entity.move(entity.vx * deltaTime, entity.vy * deltaTime);
    }
  }
};

const CombatComponent: Component = {
  name: 'combat',
  onAttach(entity) {
    entity.attackPower = 10;
    entity.defense = 5;
    entity.attack = (target: any) => {
      if (target.hasComponent('health')) {
        const damage = Math.max(1, entity.attackPower - (target.defense || 0));
        target.takeDamage(damage);
        console.log(`${entity.id} attacks ${target.id} for ${damage} damage!`);
      }
    };
  }
};

// ๐Ÿ—๏ธ Create entity classes
let Player = createEntityClass([HealthComponent, MovementComponent]);
console.log(Player.getComponents()); // ['health', 'movement']

// Add combat abilities
Player = Player.addComponent(CombatComponent);
console.log(Player.getComponents()); // ['health', 'movement', 'combat']

// Create different enemy types
const BasicEnemy = createEntityClass([HealthComponent]);
const FlyingEnemy = createEntityClass([HealthComponent, MovementComponent, PhysicsComponent]);
const EliteEnemy = createEntityClass([HealthComponent, MovementComponent, CombatComponent]);

// ๐Ÿ’ซ Game simulation
const player = new Player('Player1');
const enemy = new EliteEnemy('Enemy1');

console.log(`Player health: ${(player as any).health}`); // 100
(enemy as any).attack(player);
console.log(`Player health after attack: ${(player as any).health}`); // 95

// Update physics
const flyingEnemy = new FlyingEnemy('Flyer1');
(flyingEnemy as any).applyForce(5, -10);
flyingEnemy.update(0.016); // 60 FPS
console.log(`Flying enemy position: ${(flyingEnemy as any).x}, ${(flyingEnemy as any).y}`);

๐ŸŽฏ Summary

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

  • ๐ŸŽจ Create classes dynamically at runtime
  • ๐Ÿญ Build powerful factory functions
  • ๐Ÿ”€ Implement conditional class creation
  • ๐Ÿงฌ Design evolving class hierarchies
  • ๐Ÿ”Œ Create plugin systems with dynamic composition
  • โœจ Maintain type safety with dynamic patterns

Class expressions unlock powerful metaprogramming capabilities, allowing you to create flexible, reusable patterns that adapt to runtime conditions!