TypeScript Interfaces in OOP

Defining contracts for classes to follow

📜 What are Interfaces in OOP?

Interfaces define contracts that classes must follow. They specify what properties and methods a class should have without providing implementation. This ensures consistency and enables polymorphism across different class implementations.


// Interface definition
interface Animal {
    name: string;
    makeSound(): string;
}

// Class implementing interface
class Dog implements Animal {
    constructor(public name: string) {}

    makeSound() {
        return "Woof!";
    }
}

const dog = new Dog("Buddy");
console.log(dog.makeSound());
                                    

Output:

Woof!

Interface Features

📋

Contract

Define structure without implementation

interface Printable {
    print(): void;
}
🔗

Implementation

Classes implement interfaces

class Document implements Printable {
    print() {}
}
🔀

Multiple Interfaces

Implement multiple interfaces

class User implements 
    Printable, Saveable {}
🎯

Type Safety

Ensure correct structure at compile time

function process(item: Printable) {
    item.print();
}

🔹 Basic Interface Implementation

Create an interface and implement it in a class:

interface Vehicle {
    brand: string;
    model: string;
    year: number;
    start(): string;
    stop(): string;
}

class Car implements Vehicle {
    constructor(
        public brand: string,
        public model: string,
        public year: number
    ) {}

    start() {
        return `${this.brand} ${this.model} is starting`;
    }

    stop() {
        return `${this.brand} ${this.model} is stopping`;
    }
}

const myCar = new Car("Toyota", "Camry", 2023);
console.log(myCar.start());
console.log(myCar.stop());

Output:

Toyota Camry is starting

Toyota Camry is stopping

🔹 Multiple Interface Implementation

A class can implement multiple interfaces:

interface Printable {
    print(): string;
}

interface Saveable {
    save(): string;
}

interface Deletable {
    delete(): string;
}

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

    print() {
        return `Printing: ${this.title}`;
    }

    save() {
        return `Saving: ${this.title}`;
    }

    delete() {
        return `Deleting: ${this.title}`;
    }
}

const doc = new Document("Report", "Annual report content");
console.log(doc.print());
console.log(doc.save());
console.log(doc.delete());

Output:

Printing: Report

Saving: Report

Deleting: Report

🔹 Interface with Optional Properties

Use optional properties in interfaces:

interface User {
    id: number;
    name: string;
    email: string;
    phone?: string;        // Optional
    address?: string;      // Optional
}

class RegisteredUser implements User {
    constructor(
        public id: number,
        public name: string,
        public email: string,
        public phone?: string
    ) {}

    getContactInfo() {
        let info = `${this.name}: ${this.email}`;
        if (this.phone) {
            info += `, Phone: ${this.phone}`;
        }
        return info;
    }
}

const user1 = new RegisteredUser(1, "Alice", "[email protected]");
const user2 = new RegisteredUser(2, "Bob", "[email protected]", "555-1234");

console.log(user1.getContactInfo());
console.log(user2.getContactInfo());

Output:

Alice: [email protected]

Bob: [email protected], Phone: 555-1234

🔹 Interface Extending Other Interfaces

Interfaces can extend other interfaces:

interface Shape {
    color: string;
    getArea(): number;
}

interface Drawable extends Shape {
    draw(): string;
}

interface Resizable extends Shape {
    resize(scale: number): void;
}

class Circle implements Drawable, Resizable {
    constructor(
        public color: string,
        public radius: number
    ) {}

    getArea(): number {
        return Math.PI * this.radius * this.radius;
    }

    draw(): string {
        return `Drawing a ${this.color} circle`;
    }

    resize(scale: number): void {
        this.radius *= scale;
    }
}

const circle = new Circle("red", 5);
console.log(circle.draw());
console.log(`Area: ${circle.getArea().toFixed(2)}`);
circle.resize(2);
console.log(`New area: ${circle.getArea().toFixed(2)}`);

Output:

Drawing a red circle

Area: 78.54

New area: 314.16

🔹 Polymorphism with Interfaces

Use interfaces to work with different class implementations:

interface PaymentMethod {
    processPayment(amount: number): string;
    getTransactionFee(amount: number): number;
}

class CreditCard implements PaymentMethod {
    constructor(public cardNumber: string) {}

    processPayment(amount: number): string {
        return `Charged $${amount} to credit card ${this.cardNumber}`;
    }

    getTransactionFee(amount: number): number {
        return amount * 0.03; // 3% fee
    }
}

class PayPal implements PaymentMethod {
    constructor(public email: string) {}

    processPayment(amount: number): string {
        return `Sent $${amount} via PayPal to ${this.email}`;
    }

    getTransactionFee(amount: number): number {
        return amount * 0.025; // 2.5% fee
    }
}

class Bitcoin implements PaymentMethod {
    constructor(public walletAddress: string) {}

    processPayment(amount: number): string {
        return `Transferred $${amount} in Bitcoin to ${this.walletAddress}`;
    }

    getTransactionFee(amount: number): number {
        return 2.5; // Flat fee
    }
}

function checkout(payment: PaymentMethod, amount: number) {
    const fee = payment.getTransactionFee(amount);
    const total = amount + fee;
    console.log(payment.processPayment(amount));
    console.log(`Fee: $${fee.toFixed(2)}, Total: $${total.toFixed(2)}`);
}

const creditCard = new CreditCard("****1234");
const paypal = new PayPal("[email protected]");
const bitcoin = new Bitcoin("1A2B3C4D");

checkout(creditCard, 100);
console.log("---");
checkout(paypal, 100);
console.log("---");
checkout(bitcoin, 100);

Output:

Charged $100 to credit card ****1234

Fee: $3.00, Total: $103.00

---

Sent $100 via PayPal to [email protected]

Fee: $2.50, Total: $102.50

---

Transferred $100 in Bitcoin to 1A2B3C4D

Fee: $2.50, Total: $102.50

🔹 Interface vs Abstract Class

Use Interfaces When:

  • You only need to define a contract (no implementation)
  • You need multiple inheritance (a class can implement multiple interfaces)
  • You want to define the shape of an object
  • Unrelated classes need to follow the same contract

Use Abstract Classes When:

  • You want to share code among related classes
  • You need to provide default implementations
  • You want to use access modifiers (public, private, protected)
  • Classes share common behavior and state

🧠 Test Your Knowledge

What keyword is used to make a class follow an interface?