Principios SOLID - Dependency Inversion Principle

Inversión de Dependencias

Escrito por domin el 05/10/2024

Principio SOLID de Inversión de Dependencias (DIP)

El principio de Inversión de Dependencias (Dependency Inversion Principle - DIP) es el quinto y último de los principios SOLID. Este principio nos ayudará a crear sistemas más flexibles y fáciles de mantener al desacoplar dependencias entre clases de alto nivel y clases de bajo nivel.

¿Qué es?

El principio de Inversión de Dependencias, que a partir de ahora lo llamaremos DIP, dice que:

Esto quiere decir que las clases más importantes (las de alto nivel) no deben tener dependencias directas de clases concretas (de bajo nivel), sino que ambas deben depender de una interfaz o abstracción común. Esto permite cambiar las implementaciones concretas sin afectar al funcionamiento de la lógica principal.

Ejemplo

Supongamos que tenemos un sistema de Notificaciones, y de momento vamos a enviar estas notificaciones vía email pero hay planes para implementar en el futuro, notificaciones vía SMS.

class EmailService
{
    public function sendEmail($message)
    {
    }
}
class Notification
{
    private EmailService $emailService;

    public function __construct(EmailService $emailService)
    {
        $this->emailService = $emailService;
    }

    public function send($message)
    {
        $this->emailService->sendEmail($message);
    }
}

Hasta aquí, ¿qué tal? todo mal, ¿verdad? El problema que nos encontramos es que la clase Notification depende directamente de EmailService, una implementación muy concreta. Si más adelante queremos enviar los mensajes SMS vamos a tener que modificar la clase Notification, rompiendo el principio de versión de dependencias.

Refactor

Vamos a aplicar el DIP, a ver cómo nos queda el código.

Interface NotificationService
{
    public function send($message);
}
class EmailService implements NotificationService
{
    public function send($message)
    {
    }
}
class Notification
{
    private NotificationService $notificationService;

    public function __construct(NotificationService $notificationService)
    {
        $this->notificationService = $notificationService;
    }

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

De esta forma ahora podremos implementar cualquier medio de notificaciones:

$emailService = new EmailService();

$notification = new Notification($emailService);
$notification->send("¡Tienes una notification por email!");

Ahora implementar otros medios de notification sería tan fácil como:

class SMSService implements NotificationService
{
    public function send($message)
    {
    }
}

Y ahora haciendo solo:

$smsService = new SMSService();

$notification = new Notification($smsService);
$notification->send("¡Tienes una notification por SMS!");

¡Ya lo tendríamos!

¿Cómo lo detectamos?

Para detectar que no se está cumpliendo el DIP podemos fijarnos en las siguientes señales: