Part 4: Interfaces and Composition - Contracts and Flexibility

Introduction

I built a payment processing system with inheritance. Created Payment base class, extended it to CreditCardPayment, PayPalPayment, StripePayment. When adding support for multiple payment providers (PayPal AND Stripe), the inheritance tree broke. A payment couldn't be both PayPal and Stripe.

I refactored using interfaces and composition. Defined PaymentProcessor interface. Created separate processor classes. Payment class composed these processors instead of inheriting. Added new providers by composing new processors. No inheritance conflicts. That's when I learned "composition over inheritance."

This article teaches you TypeScript interfaces, composition patterns, dependency injection, and building flexible, testable systems.

The Problem: Rigid Inheritance

// Inheritance-based approach - inflexible
class Logger {
    log(message: string): void {
        console.log(message);
    }
}

class Service extends Logger {
    execute(): void {
        this.log('Executing service');
    }
}

// Problem 1: Can only extend one class
// class Service extends Logger, Database {}  // Error!

// Problem 2: Tightly coupled to implementation
// What if we want to log to a file instead of console?

// Problem 3: Cannot test in isolation
// Service always logs to console during tests

The Solution: Interfaces and Composition

TypeScript Interfaces

Basic Interface

Optional Properties

Readonly Properties

Method Signatures

Interface Inheritance

Composition Patterns

Strategy Pattern

Different algorithms, same interface:

Dependency Injection

Real Example: Storage Abstraction

I built a file storage system that needed to work with local files, AWS S3, and Azure Blob Storage:

Real Example: Payment Processing

Multiple payment providers with unified interface:

Multiple Interface Implementation

Best Practices

1. Program to Interfaces, Not Implementations

2. Keep Interfaces Small and Focused

3. Use Composition Over Inheritance

4. Dependency Injection for Testability

What's Next

You now understand:

  • TypeScript interfaces for contracts

  • Composition over inheritance

  • Strategy pattern for flexibility

  • Dependency injection for testability

  • Interface segregation

  • Programming to interfaces

In Part 5: SOLID Principles and Design Patterns, you'll learn the five SOLID principles, common design patterns (Factory, Singleton, Observer, etc.), and how to build production-ready, maintainable systems.


Based on building flexible TypeScript backends with clean architecture, from file storage abstraction to payment processing systems.

Last updated