Summary
Principle Name | Program to Interfaces, Not Implementations |
Principle Type | Core Principle |
Purpose | Remove 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.");
}
}
Javaclass 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.")
Pythonpackage 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.");
PHPclass Notification {
sendEmail(message: string): void {
console.log(`Email sent with message: ${message}`);
}
}
// Usage demo
const notification = new Notification();
notification.sendEmail("BigBoxCode registration success.");
TypeScriptCode 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")
Pythonfrom 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")
Pythonpackage 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