Design Principle: Program to Interfaces, Not Implementations

Summary

Principle NameProgram to Interfaces, Not Implementations
Principle Type Core Principle
PurposeRemove tight coupling by depending on interfaces,
rather than concrete classes
Implementing
Design Patterns
Adapter Pattern
Abstract Factory Pattern
Decorator Pattern
Factory Pattern
Proxy Pattern
Strategy Pattern
Related Principles Dependency Inversion Principle(DIP)
Interface Segregation Principle(ISP)
Open-Closed Principle(OCP)
Liskov Substitution Principle(LSP)
Separation of Concerns(SoC)
Single Responsibility Principle(SRP)

Definition

If we focus too much on the implementation details, then it would lead us to tightly coupled code.

If different parts of the code interact with each other through well-defined interfaces, then they depend on the interfaces, not on the other classes. And we can change the interface, and also the underlying interface implementation any time we want. That ensures loose coupling.

The “Program to Interfaces, Not Implementations” principle is-

Design your code to depend on interfaces, and avoid the dependency on concrete classes.

The process is to define interfaces that specify required behavior. Classes then implement the interface and allow the system to extend and modify the implementation, without changing the dependent code.

NOTES

An interface is a contract for classes, which declares a bunch of methods that must be implemented by the classes(implementing the interface).

Implementation

Let’s take an example of sending notifications. There are several ways we send notifications, like email, SMS, push notifications, etc.

Tightly Coupled(not recommended)

If we create a concrete Notification class and send notifications from that, then all those implementations of sending notifications is tightly coupled.

class Notification {
    public void sendEmail(String message) {
        System.out.println("Email sent with message: " + message);
    }

    public static void main(String[] args) {
        // Usage demo
        Notification notification = new Notification();
        notification.sendEmail("BigBoxCode registration success.");
    }
}
Java
class Notification:
    def send_email(self, message: str) -> None:
        print(f"Email sent with message: {message}")

# Usage demo
notification: Notification = Notification()
notification.send_email("BigBoxCode registration success.")
Python
package main

import "fmt"

type Notification struct{}

// SendEmail method for sending email
func (n Notification) SendEmail(message string) {
    fmt.Printf("Email sent with message: %s\n", message)
}

func main() {
    // Usage demo
    notification := Notification{}
    notification.SendEmail("BigBoxCode registration success.")
}
Go
<?php
class Notification {
    public function sendEmail($message) {
        echo "Email sent with message: $message\n";
    }
}

// Usage demo
$notification = new Notification();
$notification->sendEmail("BigBoxCode registration success.");
PHP
class Notification {
    sendEmail(message: string): void {
        console.log(`Email sent with message: ${message}`);
    }
}

// Usage demo
const notification = new Notification();
notification.sendEmail("BigBoxCode registration success.");
TypeScript

Code to Interface(recommended)

We can create an interface for the notification and separate classes for each notification type.

Finally, we can create a service class for notification and pass our notification object to that, for processing the notification.

// Define the interface using abstract class
abstract class Notification {
    public abstract void send(String message);
}

// Implement Notification for Email notification
class EmailSender extends Notification {
    @Override
    public void send(String message) {
        System.out.println("Email sent with message: " + message);
    }
}

// Implement Notification for SMS notification
class SMSSender extends Notification {
    @Override
    public void send(String message) {
        System.out.println("SMS sent with message: " + message);
    }
}

// NotificationService depends on the interface
class NotificationService {
    private Notification sender;

    public NotificationService(Notification sender) {
        this.sender = sender;
    }

    public void notify(String message) {
        sender.send(message);
    }

    public static void main(String[] args) {
        EmailSender emailSender = new EmailSender();
        SMSSender smsSender = new SMSSender();

        NotificationService notificationService = new NotificationService(smsSender);
        notificationService.notify("Here is your OTP: 1234");

        notificationService = new NotificationService(emailSender);
        notificationService.notify("Registration successful");
    }
}
Java
# Implementation #1
# Using Abstract Base Class

from abc import ABC, abstractmethod

# Define the interface using abstract base class
class Notification(ABC):
    @abstractmethod
    def send(self, message: str) -> None:
        pass

# Implement Notification for Email notification
class EmailSender(Notification):
    def send(self, message: str) -> None:
        print(f"Email sent with message: {message}")

# Implement notification for SMS notification
class SMSSender(Notification):
    def send(self, message: str) -> None:
        print(f"SMS sent with message: {message}")

# NotificationService depends on the interface
class NotificationService:
    def __init__(self, sender: Notification) -> None:
        self.sender = sender

    def notify(self, message: str) -> None:
        self.sender.send(message)

# Usage Demo
email_sender: EmailSender = EmailSender()
sms_sender: SMSSender = SMSSender()

notification_service: NotificationService = NotificationService(sms_sender)
notification_service.notify("Here is your OTP: 1234")

notification_service = NotificationService(email_sender)
notification_service.notify("Registration successful")
Python
# Implementation #2
# Using Protocol

from typing import Protocol

# Define the protocol
class Notification(Protocol):
    def send(self, message: str) -> None:
        ...

# Implement Notification for Email notification
class EmailSender:
    def send(self, message: str) -> None:
        print(f"Email sent with message: {message}")

# Implement Notification for SMS notification
class SMSSender:
    def send(self, message: str) -> None:
        print(f"SMS sent with message: {message}")

# NotificationService depends on the protocol
class NotificationService:
    def __init__(self, sender: Notification) -> None:
        self.sender = sender

    def notify(self, message: str) -> None:
        self.sender.send(message)

# Usage Demo
email_sender: EmailSender = EmailSender()
sms_sender: SMSSender = SMSSender()

notification_service: NotificationService = NotificationService(sms_sender)
notification_service.notify("Here is your OTP: 1234")

notification_service = NotificationService(email_sender)
notification_service.notify("Registration successful")
Python
from abc import ABC, abstractmethod

# Define the interface using abstract base class
class Notification(ABC):
    @abstractmethod
    def send(self, message: str) -> None:
        pass

# Implement Notification for Email notification
class EmailSender(Notification):
    def send(self, message: str) -> None:
        print(f"Email sent with message: {message}")

# Implement notification for SMS notification
class SMSSender(Notification):
    def send(self, message: str) -> None:
        print(f"SMS sent with message: {message}")

# NotificationService depends on the interface
class NotificationService:
    def __init__(self, sender: Notification) -> None:
        self.sender = sender

    def notify(self, message: str) -> None:
        self.sender.send(message)

# Usage Demo
email_sender: EmailSender = EmailSender()
sms_sender: SMSSender = SMSSender()

notification_service: NotificationService = NotificationService(sms_sender)
notification_service.notify("Here is your OTP: 1234")

notification_service = NotificationService(email_sender)
notification_service.notify("Registration successful")
Python
package main

import "fmt"

// Define the interface
type Notification interface {
    Send(message string)
}

// Implement Notification for Email notification
type EmailSender struct{}

func (e EmailSender) Send(message string) {
    fmt.Println("Email sent with message:", message)
}

// Implement Notification for SMS notification
type SMSSender struct{}

func (s SMSSender) Send(message string) {
    fmt.Println("SMS sent with message:", message)
}

// NotificationService depends on the interface
type NotificationService struct {
    sender Notification
}

func NewNotificationService(sender Notification) NotificationService {
    return NotificationService{sender: sender}
}

func (n NotificationService) Notify(message string) {
    n.sender.Send(message)
}

func main() {
    emailSender := EmailSender{}
    smsSender := SMSSender{}

    notificationService := NewNotificationService(smsSender)
    notificationService.Notify("Here is your OTP: 1234")

    notificationService = NewNotificationService(emailSender)
    notificationService.Notify("Registration successful")
}
Go
<?php
// Define the interface using abstract class
abstract class Notification {
    abstract public function send($message);
}

// Implement Notification for Email notification
class EmailSender extends Notification {
    public function send($message) {
        echo "Email sent with message: $message\n";
    }
}

// Implement Notification for SMS notification
class SMSSender extends Notification {
    public function send($message) {
        echo "SMS sent with message: $message\n";
    }
}

// NotificationService depends on the interface
class NotificationService {
    private $sender;

    public function __construct(Notification $sender) {
        $this->sender = $sender;
    }

    public function notify($message) {
        $this->sender->send($message);
    }
}

// Usage Demo
$emailSender = new EmailSender();
$smsSender = new SMSSender();

$notificationService = new NotificationService($smsSender);
$notificationService->notify("Here is your OTP: 1234");

$notificationService = new NotificationService($emailSender);
$notificationService->notify("Registration successful");
PHP
// Define the interface
interface Notification {
    send(message: string): void;
}

// Implement Notification for Email notification
class EmailSender implements Notification {
    send(message: string): void {
        console.log(`Email sent with message: ${message}`);
    }
}

// Implement Notification for SMS notification
class SMSSender implements Notification {
    send(message: string): void {
        console.log(`SMS sent with message: ${message}`);
    }
}

// NotificationService depends on the interface
class NotificationService {
    private sender: Notification;

    constructor(sender: Notification) {
        this.sender = sender;
    }

    notify(message: string): void {
        this.sender.send(message);
    }
}

// Usage Demo
const emailSender = new EmailSender();
const smsSender = new SMSSender();

let notificationService = new NotificationService(smsSender);
notificationService.notify("Here is your OTP: 1234");

notificationService = new NotificationService(emailSender);
notificationService.notify("Registration successful");
TypeScript

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.