Skip to content
intermediate Phase 3 · TypeScript Classes & Modules

Interfaces with Classes

Implement interfaces in classes and use abstract classes for contracts.

45m
0 problems
Topic Progress 0%

Implementing Interfaces

Implementing Interfaces

Classes can implement one or more interfaces, enforcing a contract.

Basic implements

interface Serializable {
  serialize(): string;
  deserialize(data: string): void;
}

class User implements Serializable {
  constructor(
    public name: string,
    public email: string
  ) {}

  serialize(): string {
    return JSON.stringify({ name: this.name, email: this.email });
  }

  deserialize(data: string): void {
    const obj = JSON.parse(data);
    this.name = obj.name;
    this.email = obj.email;
  }
}

Multiple Interface Implementation

interface Printable {
  print(): void;
}

interface Loggable {
  log(): void;
}

class Document implements Printable, Loggable {
  constructor(public title: string, public content: string) {}

  print(): void {
    console.log(`${this.title}: ${this.content}`);
  }

  log(): void {
    console.log(`[${new Date().toISOString()}] ${this.title}`);
  }
}

Interface as Class Contracts

Interface as Class Contracts

Repository Pattern

interface Repository<T> {
  findById(id: string): Promise<T | null>;
  findAll(): Promise<T[]>;
  create(entity: Omit<T, 'id'>): Promise<T>;
  update(id: string, data: Partial<T>): Promise<T | null>;
  delete(id: string): Promise<boolean>;
}

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

class UserRepository implements Repository<User> {
  private users: Map<string, User> = new Map();

  async findById(id: string): Promise<User | null> {
    return this.users.get(id) ?? null;
  }

  async findAll(): Promise<User[]> {
    return Array.from(this.users.values());
  }

  async create(data: Omit<User, 'id'>): Promise<User> {
    const user: User = { ...data, id: crypto.randomUUID() };
    this.users.set(user.id, user);
    return user;
  }

  async update(id: string, data: Partial<User>): Promise<User | null> {
    const user = this.users.get(id);
    if (!user) return null;
    const updated = { ...user, ...data };
    this.users.set(id, updated);
    return updated;
  }

  async delete(id: string): Promise<boolean> {
    return this.users.delete(id);
  }
}

Dependency Injection

Dependency Injection with Interfaces

Interface-Based DI

interface Logger {
  info(message: string): void;
  error(message: string, error?: Error): void;
  warn(message: string): void;
}

interface EmailService {
  send(to: string, subject: string, body: string): Promise<boolean>;
}

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

class NotificationService {
  constructor(
    private logger: Logger,
    private emailService: EmailService
  ) {}

  async notifyUser(email: string, message: string): Promise<boolean> {
    this.logger.info(`Sending notification to ${email}`);
    const sent = await this.emailService.send(email, 'Notification', message);
    if (sent) {
      this.logger.info('Notification sent successfully');
    } else {
      this.logger.error('Failed to send notification');
    }
    return sent;
  }
}

// Inject concrete implementations
const logger = new ConsoleLogger();
const emailService = new SmtpEmailService();
const notificationService = new NotificationService(logger, emailService);

Structural Compatibility

Structural Compatibility

Class Structural Typing

interface Point {
  x: number;
  y: number;
}

class MyPoint {
  constructor(public x: number, public y: number) {}
}

// MyPoint is structurally compatible with Point
function logPoint(p: Point): void {
  console.log(`${p.x}, ${p.y}`);
}

const mp = new MyPoint(10, 20);
logPoint(mp); // OK - structural compatibility

Private Members and Compatibility

class Animal {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Dog extends Animal {
  constructor(name: string, public breed: string) {
    super(name);
  }
}

// Dog is compatible with Animal (structural)
function getName(animal: Animal): string {
  return animal.name; // Error: private
}

Best Practices

  • Program to interfaces, not implementations
  • Use interfaces for all public API boundaries
  • Keep interfaces small and focused
  • Use interface extension for composing contracts
  • Favor interfaces over abstract classes when possible