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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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());
Has encontrado un error o tienes una sugerencia para mejorar esta leccion?
EscribenosTe está gustando el curso?
Tenemos cursos premium con proyectos reales y soporte personalizado.
Descubrir cursos premium