Leccion 65 de 75 18 min de lectura

Principios SOLID

SOLID es un acronimo de cinco principios de diseno orientado a objetos que ayudan a crear codigo mantenible, extensible y testeable. Estos principios son fundamentales para escribir software de calidad.

S - Single Responsibility (Responsabilidad Unica)

Una clase debe tener una sola razon para cambiar. Es decir, debe tener una unica responsabilidad.

PHP
<?php

declare(strict_types=1);

// MAL: clase con multiples responsabilidades
class Usuario
{
    public function __construct(
        public string $nombre,
        public string $email
    ) {}

    public function guardar(): void
    {
        // Logica de base de datos aqui
    }

    public function enviarEmailBienvenida(): void
    {
        // Logica de envio de email aqui
    }

    public function generarReporte(): string
    {
        // Logica de generacion de reportes
    }
}
PHP
<?php

declare(strict_types=1);

// BIEN: cada clase tiene una responsabilidad

// Solo representa los datos del usuario
class Usuario
{
    public function __construct(
        public readonly string $nombre,
        public readonly string $email
    ) {}
}

// Solo maneja persistencia
class UsuarioRepositorio
{
    public function guardar(Usuario $usuario): void { }
    public function buscarPorEmail(string $email): ?Usuario { }
}

// Solo maneja envio de emails
class EmailService
{
    public function enviarBienvenida(Usuario $usuario): void { }
}

// Solo genera reportes
class UsuarioReporte
{
    public function generar(Usuario $usuario): string { }
}

O - Open/Closed (Abierto/Cerrado)

Las clases deben estar abiertas para extension pero cerradas para modificacion. Es decir, puedes añadir nuevas funcionalidades sin modificar el codigo existente.

PHP
<?php

declare(strict_types=1);

// MAL: hay que modificar la clase para añadir nuevos descuentos
class CalculadoraDescuento
{
    public function calcular(string $tipo, float $precio): float
    {
        return match ($tipo) {
            'normal' => $precio,
            'vip' => $precio * 0.8,
            'premium' => $precio * 0.7,
            // Cada nuevo tipo requiere modificar esta clase
            default => $precio,
        };
    }
}
PHP
<?php

declare(strict_types=1);

// BIEN: abierto para extension, cerrado para modificacion

interface Descuento
{
    public function aplicar(float $precio): float;
}

class SinDescuento implements Descuento
{
    public function aplicar(float $precio): float
    {
        return $precio;
    }
}

class DescuentoVip implements Descuento
{
    public function aplicar(float $precio): float
    {
        return $precio * 0.8;
    }
}

class DescuentoPremium implements Descuento
{
    public function aplicar(float $precio): float
    {
        return $precio * 0.7;
    }
}

// Nuevo descuento sin modificar código existente
class DescuentoBlackFriday implements Descuento
{
    public function aplicar(float $precio): float
    {
        return $precio * 0.5;
    }
}

class CalculadoraDescuento
{
    public function calcular(Descuento $descuento, float $precio): float
    {
        return $descuento->aplicar($precio);
    }
}

L - Liskov Substitution (Sustitucion de Liskov)

Las clases hijas deben poder usarse en lugar de sus clases padre sin alterar el comportamiento esperado del programa.

PHP
<?php

declare(strict_types=1);

// MAL: viola el principio de Liskov
class Ave
{
    public function volar(): void
    {
        echo "Volando...\n";
    }
}

class Pinguino extends Ave
{
    public function volar(): void
    {
        // Los pinguinos no vuelan!
        throw new Exception('Los pinguinos no pueden volar');
    }
}

// Esto falla inesperadamente
function hacerVolar(Ave $ave): void
{
    $ave->volar(); // Error si es un pinguino
}
PHP
<?php

declare(strict_types=1);

// BIEN: jerarquia correcta

interface Ave
{
    public function comer(): void;
}

interface AveVoladora extends Ave
{
    public function volar(): void;
}

class Aguila implements AveVoladora
{
    public function comer(): void
    {
        echo "Comiendo...\n";
    }

    public function volar(): void
    {
        echo "Volando...\n";
    }
}

class Pinguino implements Ave
{
    public function comer(): void
    {
        echo "Comiendo pescado...\n";
    }

    public function nadar(): void
    {
        echo "Nadando...\n";
    }
}

// Ahora es seguro
function hacerVolar(AveVoladora $ave): void
{
    $ave->volar(); // Siempre funciona
}

I - Interface Segregation (Segregacion de Interfaces)

Es mejor tener muchas interfaces especificas que una interfaz general. Las clases no deberian depender de metodos que no usan.

PHP
<?php

declare(strict_types=1);

// MAL: interfaz demasiado grande
interface Trabajador
{
    public function trabajar(): void;
    public function comer(): void;
    public function dormir(): void;
    public function programar(): void;
    public function disenar(): void;
}

// Un programador no disena
class Programador implements Trabajador
{
    public function trabajar(): void { }
    public function comer(): void { }
    public function dormir(): void { }
    public function programar(): void { }

    public function disenar(): void
    {
        // Obligado a implementar algo que no hace
        throw new Exception('No soy disenador');
    }
}
PHP
<?php

declare(strict_types=1);

// BIEN: interfaces pequenas y especificas

interface Trabajador
{
    public function trabajar(): void;
}

interface Comedor
{
    public function comer(): void;
}

interface Programable
{
    public function programar(): void;
}

interface Disenable
{
    public function disenar(): void;
}

// Solo implementa lo que necesita
class Programador implements Trabajador, Comedor, Programable
{
    public function trabajar(): void
    {
        $this->programar();
    }

    public function comer(): void { }
    public function programar(): void { }
}

class Disenador implements Trabajador, Comedor, Disenable
{
    public function trabajar(): void
    {
        $this->disenar();
    }

    public function comer(): void { }
    public function disenar(): void { }
}

D - Dependency Inversion (Inversión de Dependencias)

Las clases de alto nivel no deben depender de clases de bajo nivel. Ambas deben depender de abstracciones (interfaces).

PHP
<?php

declare(strict_types=1);

// MAL: dependencia directa de clase concreta
class MySQLDatabase
{
    public function insertar(string $tabla, array $datos): void { }
}

class UsuarioService
{
    private MySQLDatabase $db;

    public function __construct()
    {
        // Acoplamiento fuerte a MySQL
        $this->db = new MySQLDatabase();
    }

    public function crear(array $datos): void
    {
        $this->db->insertar('usuarios', $datos);
    }
}

// Imposible usar PostgreSQL o hacer tests sin MySQL real
PHP
<?php

declare(strict_types=1);

// BIEN: depender de abstracciones

interface Database
{
    public function insertar(string $tabla, array $datos): void;
}

class MySQLDatabase implements Database
{
    public function insertar(string $tabla, array $datos): void { }
}

class PostgreSQLDatabase implements Database
{
    public function insertar(string $tabla, array $datos): void { }
}

class UsuarioService
{
    public function __construct(
        private readonly Database $db  // Depende de la interfaz
    ) {}

    public function crear(array $datos): void
    {
        $this->db->insertar('usuarios', $datos);
    }
}

// Ahora es flexible y testeable
$mysql = new MySQLDatabase();
$service = new UsuarioService($mysql);

$postgres = new PostgreSQLDatabase();
$service = new UsuarioService($postgres);

Ejercicios

Ejercicio 1: Aplicar SRP

La siguiente clase tiene multiples responsabilidades. Dividela en clases con responsabilidad unica:

PHP
<?php

declare(strict_types=1);

class Pedido
{
    public array $items = [];
    public float $total = 0;

    public function agregarItem(string $nombre, float $precio): void
    {
        $this->items[] = ['nombre' => $nombre, 'precio' => $precio];
        $this->total += $precio;
    }

    public function guardarEnBD(): void
    {
        // INSERT INTO pedidos...
    }

    public function enviarEmailConfirmacion(string $email): void
    {
        // mail($email, ...)
    }

    public function generarFacturaPDF(): string
    {
        // Generar PDF...
        return 'factura.pdf';
    }
}
Ver solucion
PHP
<?php

declare(strict_types=1);

// Clase que solo representa el pedido
class Pedido
{
    public array $items = [];
    public float $total = 0;

    public function agregarItem(string $nombre, float $precio): void
    {
        $this->items[] = ['nombre' => $nombre, 'precio' => $precio];
        $this->total += $precio;
    }
}

// Repositorio para persistencia
class PedidoRepositorio
{
    public function guardar(Pedido $pedido): void
    {
        // INSERT INTO pedidos...
    }
}

// Servicio de notificaciones
class NotificadorPedido
{
    public function enviarConfirmacion(Pedido $pedido, string $email): void
    {
        // mail($email, ...)
    }
}

// Generador de facturas
class GeneradorFactura
{
    public function generarPDF(Pedido $pedido): string
    {
        // Generar PDF...
        return 'factura.pdf';
    }
}

Ejercicio 2: Aplicar OCP

Refactoriza este codigo para que se puedan añadir nuevos tipos de notificacion sin modificar la clase existente:

PHP
<?php

declare(strict_types=1);

class Notificador
{
    public function enviar(string $tipo, string $mensaje, string $destino): void
    {
        match ($tipo) {
            'email' => $this->enviarEmail($mensaje, $destino),
            'sms' => $this->enviarSMS($mensaje, $destino),
            'push' => $this->enviarPush($mensaje, $destino),
            default => throw new InvalidArgumentException('Tipo no soportado'),
        };
    }

    private function enviarEmail(string $mensaje, string $destino): void { }
    private function enviarSMS(string $mensaje, string $destino): void { }
    private function enviarPush(string $mensaje, string $destino): void { }
}
Ver solucion
PHP
<?php

declare(strict_types=1);

interface CanalNotificacion
{
    public function enviar(string $mensaje, string $destino): void;
}

class NotificacionEmail implements CanalNotificacion
{
    public function enviar(string $mensaje, string $destino): void
    {
        // Enviar email
    }
}

class NotificacionSMS implements CanalNotificacion
{
    public function enviar(string $mensaje, string $destino): void
    {
        // Enviar SMS
    }
}

class NotificacionPush implements CanalNotificacion
{
    public function enviar(string $mensaje, string $destino): void
    {
        // Enviar push
    }
}

// Nuevo canal sin modificar nada existente
class NotificacionSlack implements CanalNotificacion
{
    public function enviar(string $mensaje, string $destino): void
    {
        // Enviar a Slack
    }
}

class Notificador
{
    public function enviar(
        CanalNotificacion $canal,
        string $mensaje,
        string $destino
    ): void {
        $canal->enviar($mensaje, $destino);
    }
}

Ejercicio 3: Aplicar DIP

Refactoriza para que la clase de alto nivel no dependa de la implementacion concreta:

PHP
<?php

declare(strict_types=1);

class FileLogger
{
    public function log(string $mensaje): void
    {
        file_put_contents('app.log', $mensaje . "\n", FILE_APPEND);
    }
}

class ProcesadorPagos
{
    private FileLogger $logger;

    public function __construct()
    {
        $this->logger = new FileLogger();
    }

    public function procesar(float $monto): void
    {
        $this->logger->log("Procesando pago de $monto");
        // Procesar...
        $this->logger->log("Pago completado");
    }
}
Ver solucion
PHP
<?php

declare(strict_types=1);

// Abstraccion
interface Logger
{
    public function log(string $mensaje): void;
}

// Implementacion concreta
class FileLogger implements Logger
{
    public function log(string $mensaje): void
    {
        file_put_contents('app.log', $mensaje . "\n", FILE_APPEND);
    }
}

// Otra implementacion
class DatabaseLogger implements Logger
{
    public function log(string $mensaje): void
    {
        // INSERT INTO logs...
    }
}

// Depende de la abstraccion
class ProcesadorPagos
{
    public function __construct(
        private readonly Logger $logger
    ) {}

    public function procesar(float $monto): void
    {
        $this->logger->log("Procesando pago de $monto");
        // Procesar...
        $this->logger->log("Pago completado");
    }
}

// Uso flexible
$procesador = new ProcesadorPagos(new FileLogger());
$procesador = new ProcesadorPagos(new DatabaseLogger());

Te está gustando el curso?

Tenemos cursos premium con proyectos reales y soporte personalizado.

Descubrir cursos premium