Diseño de software que no se rompe con el tiempo
Cuando hablamos de transformación digital o infraestructura IT, muchas veces pensamos en servidores, redes y virtualización. Pero nada de eso tiene sentido si el software que corre arriba está mal diseñado.
Ahí entra SOLID, un conjunto de principios de diseño de software propuestos por Robert C. Martin (Uncle Bob) que ayudan a crear sistemas mantenibles, extensibles y robustos. Estos principios no solo mejoran la calidad del código, sino que reducen costos de mantenimiento y facilitan el testing. Visiten su sitio web en: http://cleancoder.com/
Veamos cada principio con ejemplos en PHP.
1. Single Responsibility Principle (SRP)
"Una clase debe tener una sola razón para cambiar."
Beneficio: Facilita el testing, reduce acoplamiento y mejora la mantenibilidad.]
Ejemplo incorrecto:
class Report {
public function generate() {
// Genera el reporte
return "Reporte de ventas: " . date('Y-m-d H:i:s');
}
public function saveToFile($filename) {
// Guarda en archivo
file_put_contents($filename, $this->generate());
}
public function sendByEmail($email) {
// Envía por correo
mail($email, "Reporte", $this->generate());
}
}
👉 Esta clase hace demasiado: genera, guarda y envía.
Ejemplo correcto:
class Report {
private $data;
public function __construct($data) {
$this->data = $data;
}
public function generate() {
// Genera el reporte
return "Reporte de ventas: " . date('Y-m-d H:i:s') . "\n" .
implode("\n", $this->data);
}
}
class ReportSaver {
public function saveToFile(Report $report, $filename) {
// Guarda en archivo
file_put_contents($filename, $report->generate());
}
}
class ReportMailer {
public function sendByEmail(Report $report, $email) {
// Envía por correo
mail($email, "Reporte Diario", $report->generate());
}
}
Cada clase tiene una sola responsabilidad.
2. Open/Closed Principle (OCP)
"El código debe estar abierto a extensión, pero cerrado a modificación."
Beneficio: Agregar nuevas funcionalidades sin riesgo de romper código existente.
Ejemplo incorrecto:
class Discount {
public function apply($type, $amount) {
if ($type == "student") {
return $amount * 0.9;
} elseif ($type == "vip") {
return $amount * 0.8;
}
return $amount;
}
}
Cada vez que agrego un nuevo descuento tengo que modificar la clase.
Ejemplo correcto con polimorfismo:
interface Discount {
public function apply($amount);
public function getDescription();
}
class StudentDiscount implements Discount {
public function apply($amount) {
return $amount * 0.9;
}
public function getDescription() {
return "Descuento estudiantil 10%";
}
}
class VipDiscount implements Discount {
public function apply($amount) {
return $amount * 0.8;
}
public function getDescription() {
return "Descuento VIP 20%";
}
}
class SeniorDiscount implements Discount {
public function apply($amount) {
return $amount * 0.85;
}
public function getDescription() {
return "Descuento tercera edad 15%";
}
}
Ahora agrego descuentos sin tocar lo existente, solo extiendo.
3. Liskov Substitution Principle (LSP)
"Las clases hijas deben poder sustituir a sus padres sin romper el sistema."
Beneficio: Garantiza que el polimorfismo funcione correctamente.
Ejemplo incorrecto:
class Bird {
public function fly() {
echo "Flying";
}
}
class Penguin extends Bird {
public function fly() {
throw new Exception("Penguins can't fly!");
}
}
👉 Rompemos el principio porque un Penguin no puede comportarse como un Bird que vuela.
Ejemplo correcto:
abstract class Bird {
protected $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
class FlyingBird extends Bird {
public function fly() {
echo $this->name . " is flying";
}
}
class Penguin extends Bird {
public function swim() {
echo $this->name . " is swimming";
}
}
Ahora cada clase cumple con el contrato esperado sin romper la funcionalidad heredada.
4. Interface Segregation Principle (ISP)
"Es mejor muchas interfaces pequeñas que una gigante."
Beneficio: Las clases solo implementan lo que realmente necesitan.
Ejemplo incorrecto:
interface Worker {
public function work();
public function eat();
}
class Robot implements Worker {
public function work() {
echo "Working";
}
public function eat() {
// ¿Un robot comiendo?
throw new Exception("Robots don't eat!");
}
}
👉 Forzamos a implementar métodos que no aplican.
Ejemplo correcto:
interface Workable {
public function work();
}
interface Eatable {
public function eat();
}
class Human implements Workable, Eatable {
public function work() { echo "Human working hard"; }
public function eat() { echo "Human eating lunch"; }
}
class Robot implements Workable {
public function work() { echo "Robot processing tasks 24/7"; }
}
Cada clase implementa solo lo que necesita.
5. Dependency Inversion Principle (DIP)
"Depender de abstracciones, no de implementaciones concretas."
Beneficio: Facilita el testing con mocks y permite cambiar implementaciones fácilmente.
Ejemplo incorrecto:
class MySQLDatabase {
public function connect() {
// Conexión MySQL
return new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
}
}
class UserRepository {
private $db;
public function __construct() {
$this->db = new MySQLDatabase();
}
public function findUser($id) {
$connection = $this->db->connect();
// ... lógica de búsqueda
return "User found";
}
}
👉 Estamos atados a MySQL.
Ejemplo correcto:
interface Database {
public function connect();
}
class MySQLDatabase implements Database {
public function connect() {
return new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
}
}
class PostgreSQLDatabase implements Database {
public function connect() {
return new PDO('pgsql:host=localhost;dbname=test', 'user', 'pass');
}
}
class UserRepository {
private $db;
public function __construct(Database $db) {
$this->db = $db;
}
public function findUser($id) {
$connection = $this->db->connect();
// ... lógica de búsqueda
return "User found";
}
}
Ahora podemos inyectar cualquier base de datos.
Ejemplo de uso mas completo
// Creando las dependencias
$database = new MySQLDatabase();
$userRepo = new UserRepository($database);
// Aplicando descuentos
$discount = new StudentDiscount();
$finalAmount = $discount->apply(100);
// Generando reportes
$reportData = ["Venta 1: $50", "Venta 2: $75"];
$report = new Report($reportData);
$saver = new ReportSaver();
$mailer = new ReportMailer();
$saver->saveToFile($report, "reporte_" . date('Y-m-d') . ".txt");
$mailer->sendByEmail($report, "admin@empresa.com");
Conclusión: SOLID es más que teoría
Aplicar SOLID en PHP (y en cualquier lenguaje) no es un lujo académico. Es la diferencia entre un sistema que se rompe con cada cambio y uno que crece sin miedo.
Como consultor en Infraestructura IT y desarrollo de sistemas, lo veo todo el tiempo: las empresas gastan fortunas en hardware y redes, pero el software falla porque fue escrito sin principios.
Un buen diseño hoy es menos deuda técnica mañana. Además, facilita el testing automatizado, reduce el tiempo de desarrollo de nuevas funcionalidades y mejora la satisfacción del equipo de desarrollo.
¿Has aplicado SOLID en tus proyectos? ¿Cuál principio te resulta más difícil de implementar? Comparte tu experiencia en los comentarios.
Comentarios