Program to interface NOT implementation

Essaadani Younes
4 min readAug 13, 2024

--

Program to interface NOT implementation

Program to an Interface, Not an Implementation is a fundamental software design principle that enhances flexibility and maintainability. By coding to interfaces rather than specific implementations, developers decouple systems from their dependencies, making the code more modular, testable, and scalable. This approach aligns with key design principles like the Open/Closed Principle, allowing systems to be easily extended without altering existing code. In this article, we’ll explore how this principle works and why it’s essential for building robust software.

In this article we will use a notification example such as SMS, emailing, But first I’ll show you the problem without using this principle of this article then we will solve it using same principle, get your self some drink and follow up with me.

Without the principle

Let’s create a service that send notifications via SMS

// app/Services/SmsNotificationService.php

namespace App\Services;

use App\Models\User;

class SmsNotificationService{

public function sendRegistrationNotification(User $user){
// Retrieve user phone number from user
// Send sms to a user about his registration
}
public function subscriptionExpired(User $user, User $admin){
// Retrieve user phone number from user
// Send sms to a user that his subscription expired

// Retrieve user phone number from admin
// Notify admin that a client subscription expired or something..
}
}

Now let’s consume this service in our controller & command

// app/Http/Controllers/AuthController.php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Services\SmsNotificationService;
use Illuminate\Http\Request;

class AuthController extends Controller{

public function __construct(private SmsNotificationService $smsService){
}

public function Register(Request $request){
//Register user
...
// Send him notification about the registration
$this->smsService->sendRegistrationNotification($user);
}

}
// app/Console/Commands/CheckSubscriptionExpirationCommand.php
// Assume this command registered in the Kernel and runs every day

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\SmsNotificationService;

class CheckSubscriptionExpirationCommand extends Command{
protected $signature = 'subscriptions:check';

public function handle(SmsNotificationService $smsService)
{
$adminUser = //Retrieve admin
$expiredUsers = //Retrieve users with expired subscriptions

//Map through users
//Pass each user to the service to send him expiration notification
$smsService->subscriptionExpired($user, $admin);

return 0;
}
}

Well you did great job your application is running, then the owner changes his mind and want to change the notification to go through Emails now not SMS, HMMMM you got to go and replace all places you used to send notification using SMS good luck trying! Or wait I have a way for you let’s begin.

With the principle

First let’s begin with the base concept which to have an interface that tells us what the implementations should have

// app/Interfaces/INotification.php

namespace App\Interfaces;

use App\Models\User;

class INotification{
public function sendRegistrationNotification(User $user);
public function subscriptionExpired(User $user, User $admin);
}

Let’s modify our SMS service and Introduce the new service for email notification

// app/Services/SmsNotificationService.php

namespace App\Services;

use App\Models\User;
use App\Interfaces\INotification;

class SmsNotificationService implements INotification {

public function sendRegistrationNotification(User $user){
// Retrieve user phone number from user
// Send sms to a user about his registration
}
public function subscriptionExpired(User $user, User $admin){
// Retrieve user phone number from user
// Send sms to a user that his subscription expired

// Retrieve user phone number from admin
// Notify admin that a client subscription expired or something..
}
}
// app/Services/EmailNotificationService.php

namespace App\Services;

use App\Models\User;
use App\Interfaces\INotification;

class EmailNotificationService implements INotification {

public function sendRegistrationNotification(User $user){
// Retrieve user email from user
// Send email to a user about his registration
}
public function subscriptionExpired(User $user, User $admin){
// Retrieve user email from user
// Send email to a user that his subscription expired

// Retrieve user email from admin
// Notify admin that a client subscription expired or something..
}
}

And we should modify our controller and command

// app/Http/Controllers/AuthController.php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Interfaces\INotification;
use Illuminate\Http\Request;

class AuthController extends Controller{

public function __construct(private INotification $notificationService){
}

public function Register(Request $request){
//Register user
...
// Send him notification about the registration
$this->notificationService->sendRegistrationNotification($user);
}

}
// app/Console/Commands/CheckSubscriptionExpirationCommand.php
// Assume this command registered in the Kernel and runs every day

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Interfaces\INotification;

class CheckSubscriptionExpirationCommand extends Command{
protected $signature = 'subscriptions:check';

public function handle(INotification $notificationService)
{
$adminUser = //Retrieve admin
$expiredUsers = //Retrieve users with expired subscriptions

//Map through users
//Pass each user to the service to send him expiration notification
$notificationService->subscriptionExpired($user, $admin);

return 0;
}
}

Okay but how we know which implementation the framework will use?
Got you this where service container role comes in!

In you AppServiceProvide.php do the follows.

// app/Provides/AppServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Interfaces\INotification;
use App\Services\EmailNotificationService;

class AppServiceProvider extends ServiceProvider{

public function register(){

$this->app->bind(
INotification::class,
EmailNotificationService::class
);
}
}

Now we are telling Laravel that whenever we want to use the INotification Give it EmailNotificationService implementation, and with that whenever you want to add or change which implementation the users want you change it in only one place and your application doesn’t break down! Great work.

Adopting the principle of “Program to an Interface, Not an Implementation” is a crucial step toward creating flexible, maintainable, and scalable software. By decoupling code from specific implementations, developers can more easily adapt to changes, introduce new features, and ensure their systems are resilient to future requirements. This pattern not only promotes better design practices but also leads to more robust and reusable code. As you continue to refine your development process, embracing this principle will help you build software that is easier to manage and extend, ultimately leading to higher-quality products.

--

--