Inheritance and Polymorphism
Master advanced OOP concepts that enable code reuse and flexible design through inheritance hierarchies and polymorphic behavior.
Inheritance Hierarchies
Inheritance creates a parent-child relationship between classes, allowing child classes to inherit and extend parent functionality.
// Base class
class User {
protected id: string;
protected email: string;
protected createdAt: Date;
constructor(id: string, email: string) {
this.id = id;
this.email = email;
this.createdAt = new Date();
}
public getProfile(): string {
return `User: ${this.email} (ID: ${this.id})`;
}
public canAccessResource(resourceId: string): boolean {
return false; // Base users can't access resources
}
}
// First level inheritance
class RegisteredUser extends User {
protected username: string;
protected lastLogin: Date;
constructor(id: string, email: string, username: string) {
super(id, email);
this.username = username;
this.lastLogin = new Date();
}
public override getProfile(): string {
return `${this.username} (${this.email})`;
}
public override canAccessResource(resourceId: string): boolean {
return true; // Registered users can access basic resources
}
public updateLastLogin(): void {
this.lastLogin = new Date();
}
}
// Second level inheritance
class PremiumUser extends RegisteredUser {
private subscriptionEndDate: Date;
private features: string[];
constructor(
id: string,
email: string,
username: string,
subscriptionMonths: number
) {
super(id, email, username);
this.subscriptionEndDate = new Date();
this.subscriptionEndDate.setMonth(
this.subscriptionEndDate.getMonth() + subscriptionMonths
);
this.features = ["ad-free", "HD-streaming", "offline-mode"];
}
public override getProfile(): string {
return `⭐ PREMIUM: ${super.getProfile()}`;
}
public override canAccessResource(resourceId: string): boolean {
// Premium users can access all resources
return this.isSubscriptionActive();
}
private isSubscriptionActive(): boolean {
return new Date() < this.subscriptionEndDate;
}
public getFeatures(): string[] {
return [...this.features];
}
}
class AdminUser extends RegisteredUser {
private permissions: Set<string>;
constructor(id: string, email: string, username: string) {
super(id, email, username);
this.permissions = new Set(["read", "write", "delete", "admin"]);
}
public override getProfile(): string {
return `🛡️ ADMIN: ${super.getProfile()}`;
}
public override canAccessResource(resourceId: string): boolean {
return true; // Admins can access everything
}
public hasPermission(permission: string): boolean {
return this.permissions.has(permission);
}
public deleteUser(userId: string): void {
console.log(`Admin deleted user: ${userId}`);
}
}
// Usage
const users: User[] = [
new User("1", "guest@example.com"),
new RegisteredUser("2", "user@example.com", "john_doe"),
new PremiumUser("3", "premium@example.com", "jane_premium", 12),
new AdminUser("4", "admin@example.com", "admin_user")
];
users.forEach(user => {
console.log(user.getProfile());
console.log(`Can access: ${user.canAccessResource("resource-1")}`);
});Method Overriding
Child classes can provide their own implementation of methods inherited from parent classes.
class PaymentProcessor {
protected processingFee: number = 0;
processPayment(amount: number): number {
const fee = this.calculateFee(amount);
const total = amount + fee;
console.log(`Processing payment: $${amount} + $${fee} fee = $${total}`);
return total;
}
protected calculateFee(amount: number): number {
return 0; // Base class has no fee
}
}
class CreditCardProcessor extends PaymentProcessor {
protected override calculateFee(amount: number): number {
return amount * 0.029 + 0.30; // 2.9% + $0.30
}
}
class PayPalProcessor extends PaymentProcessor {
protected override calculateFee(amount: number): number {
return amount * 0.034 + 0.49; // 3.4% + $0.49
}
}
class WireTransferProcessor extends PaymentProcessor {
protected override calculateFee(amount: number): number {
return 25.00; // Flat fee
}
}
// Same method, different behavior based on object type
function processAllPayments(processors: PaymentProcessor[], amount: number): void {
processors.forEach(processor => {
processor.processPayment(amount);
});
}
const processors = [
new PaymentProcessor(),
new CreditCardProcessor(),
new PayPalProcessor(),
new WireTransferProcessor()
];
processAllPayments(processors, 100);Abstract Classes
Abstract classes cannot be instantiated directly and are meant to be extended. They can contain abstract methods that must be implemented by child classes.
abstract class DatabaseConnection {
protected connectionString: string;
protected isConnected: boolean = false;
constructor(connectionString: string) {
this.connectionString = connectionString;
}
// Abstract methods - must be implemented by subclasses
abstract connect(): Promise<void>;
abstract disconnect(): Promise<void>;
abstract query(sql: string): Promise<any[]>;
abstract execute(sql: string): Promise<number>;
// Concrete method - shared by all connections
public getStatus(): string {
return this.isConnected ? "Connected" : "Disconnected";
}
protected log(message: string): void {
console.log(`[${new Date().toISOString()}] ${message}`);
}
}
class MySQLConnection extends DatabaseConnection {
async connect(): Promise<void> {
this.log(`Connecting to MySQL: ${this.connectionString}`);
// Simulate connection
await new Promise(resolve => setTimeout(resolve, 100));
this.isConnected = true;
this.log("MySQL connected successfully");
}
async disconnect(): Promise<void> {
this.log("Disconnecting from MySQL");
this.isConnected = false;
}
async query(sql: string): Promise<any[]> {
if (!this.isConnected) throw new Error("Not connected");
this.log(`Executing query: ${sql}`);
return []; // Simulated result
}
async execute(sql: string): Promise<number> {
if (!this.isConnected) throw new Error("Not connected");
this.log(`Executing command: ${sql}`);
return 1; // Simulated affected rows
}
}
class PostgreSQLConnection extends DatabaseConnection {
async connect(): Promise<void> {
this.log(`Connecting to PostgreSQL: ${this.connectionString}`);
await new Promise(resolve => setTimeout(resolve, 100));
this.isConnected = true;
this.log("PostgreSQL connected successfully");
}
async disconnect(): Promise<void> {
this.log("Disconnecting from PostgreSQL");
this.isConnected = false;
}
async query(sql: string): Promise<any[]> {
if (!this.isConnected) throw new Error("Not connected");
this.log(`PG Query: ${sql}`);
return [];
}
async execute(sql: string): Promise<number> {
if (!this.isConnected) throw new Error("Not connected");
this.log(`PG Execute: ${sql}`);
return 1;
}
}
// Usage
async function databaseExample() {
const connections: DatabaseConnection[] = [
new MySQLConnection("mysql://localhost:3306/mydb"),
new PostgreSQLConnection("postgresql://localhost:5432/mydb")
];
for (const conn of connections) {
await conn.connect();
await conn.query("SELECT * FROM users");
console.log(`Status: ${conn.getStatus()}`);
await conn.disconnect();
}
}Interfaces vs Abstract Classes
Understanding when to use interfaces versus abstract classes:
// Interface: Contract for implementation
interface Printable {
print(): void;
getPrintData(): string;
}
// Interface: Can extend multiple interfaces
interface Saveable {
save(): Promise<void>;
load(): Promise<void>;
}
// A class can implement multiple interfaces
class Document implements Printable, Saveable {
constructor(
private content: string,
private filename: string
) {}
print(): void {
console.log(this.getPrintData());
}
getPrintData(): string {
return `Document: ${this.filename}\n${this.content}`;
}
async save(): Promise<void> {
console.log(`Saving ${this.filename}...`);
// Save logic
}
async load(): Promise<void> {
console.log(`Loading ${this.filename}...`);
// Load logic
}
}
// Abstract class: Provides base implementation
abstract class Report {
protected title: string;
protected generatedAt: Date;
constructor(title: string) {
this.title = title;
this.generatedAt = new Date();
}
// Concrete method with implementation
public getMetadata(): string {
return `Report: ${this.title} (Generated: ${this.generatedAt.toISOString()})`;
}
// Abstract method - must be implemented
abstract generateContent(): string;
abstract export(format: string): string;
}
class SalesReport extends Report {
constructor(
title: string,
private salesData: any[]
) {
super(title);
}
generateContent(): string {
return `Sales Report Content: ${this.salesData.length} records`;
}
export(format: string): string {
return `Exporting sales report to ${format}`;
}
}
// When to use which:
// - Use INTERFACE when you need multiple inheritance or pure contracts
// - Use ABSTRACT CLASS when you have shared implementation and stateComposition over Inheritance
While inheritance is powerful, composition is often preferred for building flexible systems.
// Instead of deep inheritance, use composition
interface Logger {
log(message: string): void;
}
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(`[LOG] ${message}`);
}
}
class FileLogger implements Logger {
constructor(private filename: string) {}
log(message: string): void {
console.log(`[FILE: ${this.filename}] ${message}`);
}
}
interface Cache {
get(key: string): any;
set(key: string, value: any): void;
}
class MemoryCache implements Cache {
private store = new Map();
get(key: string): any {
return this.store.get(key);
}
set(key: string, value: any): void {
this.store.set(key, value);
}
}
// Using composition
class UserService {
constructor(
private logger: Logger,
private cache: Cache
) {}
async getUser(id: string): Promise<any> {
this.logger.log(`Fetching user: ${id}`);
// Check cache first
const cached = this.cache.get(`user_${id}`);
if (cached) {
this.logger.log(`User ${id} found in cache`);
return cached;
}
// Fetch from database (simulated)
const user = { id, name: "John Doe" };
this.cache.set(`user_${id}`, user);
this.logger.log(`User ${id} cached`);
return user;
}
}
// Flexible - can swap implementations easily
const service1 = new UserService(
new ConsoleLogger(),
new MemoryCache()
);
const service2 = new UserService(
new FileLogger("app.log"),
new MemoryCache()
);Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free