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