Skip to main content

Command Palette

Search for a command to run...

Decoupling Class Dependency Using Interface: A Guide with TypeScript and PHP Examples [2024]

Updated
Decoupling Class Dependency Using Interface: A Guide with TypeScript and PHP Examples [2024]

In software development, one of the pillars of creating maintainable, scalable applications is reducing class dependencies. A tightly coupled system is hard to modify and test, as changing one component often affects many others. Decoupling dependencies through interfaces is an effective way to achieve flexibility and scalability.

This blog post explains how to decouple class dependencies using interfaces, with examples in TypeScript and PHP.

What Is Decoupling?

Decoupling is the process of reducing direct dependencies between components. When two classes are tightly coupled, one depends on the implementation details of the other.

By introducing an interface, you create a contract that defines behavior without specifying how it is implemented.

Why Use Interfaces for Decoupling?

1. Flexibility

Interfaces allow you to change the implementation of a dependency without modifying the dependent class.

2. Testability

You can easily mock dependencies when testing because the dependent class only interacts with the interface.

3. Maintainability

Decoupled code is easier to maintain and extend. Adding new features often requires fewer changes.

Example 1: Decoupling with Interfaces in TypeScript

Step 1: Define the Interface

interface PaymentProcessor {
  processPayment(amount: number): void;
}

Step 2: Implement the Interface

class StripePaymentProcessor implements PaymentProcessor {
  processPayment(amount: number): void {
    console.log(`Processing payment of $${amount} through Stripe.`);
  }
}

class PayPalPaymentProcessor implements PaymentProcessor {
  processPayment(amount: number): void {
    console.log(`Processing payment of $${amount} through PayPal.`);
  }
}

Step 3: Use Dependency Injection

class PaymentService {
  private processor: PaymentProcessor;

  constructor(processor: PaymentProcessor) {
    this.processor = processor;
  }

  makePayment(amount: number): void {
    this.processor.processPayment(amount);
  }
}

// Usage
const stripeProcessor = new StripePaymentProcessor();
const paymentService = new PaymentService(stripeProcessor);
paymentService.makePayment(100);

Example 2: Decoupling with Interfaces in PHP

Step 1: Define the Interface

interface PaymentProcessor {
    public function processPayment(float $amount): void;
}

Step 2: Implement the Interface

class StripePaymentProcessor implements PaymentProcessor {
    public function processPayment(float $amount): void {
        echo "Processing payment of $$amount through Stripe.\n";
    }
}

class PayPalPaymentProcessor implements PaymentProcessor {
    public function processPayment(float $amount): void {
        echo "Processing payment of $$amount through PayPal.\n";
    }
}

Step 3: Use Dependency Injection

class PaymentService {
    private PaymentProcessor $processor;

    public function __construct(PaymentProcessor $processor) {
        $this->processor = $processor;
    }

    public function makePayment(float $amount): void {
        $this->processor->processPayment($amount);
    }
}

// Usage
$stripeProcessor = new StripePaymentProcessor();
$paymentService = new PaymentService($stripeProcessor);
$paymentService->makePayment(100.0);

Just like in TypeScript, PaymentService depends on PaymentProcessor, not a specific payment gateway.

Advantages of This Approach

  • Interchangeability: Swap one implementation for another with minimal code changes.

  • Ease of Testing: Replace the real implementation with a mock or stub during testing.

  • Open/Closed Principle: Extend functionality by adding new interface implementations without altering existing code.

Decoupling class dependencies with interfaces is crucial for building modular, testable, and scalable applications. By relying on abstractions instead of concrete implementations, you allow your code to adapt seamlessly to changes and new requirements.

The examples in TypeScript and PHP demonstrate how this principle works in practice. Whether you're using a statically typed language like TypeScript or a dynamically typed one like PHP, this approach improves the overall quality of your codebase.

More from this blog

M

Makeinfo Blog | Tech Guides, SEO Tips, and Marketing Automation

127 posts

Blogging helpful tech tips, how-to tutorials since 2020