Que son los Principios SOLID
SOLID son 5 principios de diseno orientado a objetos propuestos por Robert C. Martin (Uncle Bob). Son directrices para disenar software con alta mantenibilidad, extensibilidad y facilidad de prueba.
S - Single Responsibility Principle (Principio de Responsabilidad Unica)
O - Open/Closed Principle (Principio Abierto/Cerrado)
L - Liskov Substitution Principle (Principio de Sustitucion de Liskov)
I - Interface Segregation Principle (Principio de Segregacion de Interfaces)
D - Dependency Inversion Principle (Principio de Inversion de Dependencias)
1. Principio de Responsabilidad Unica (SRP)
Una clase debe tener solo una responsabilidad. Debe haber solo una razon para cambiar.
Ejemplo de Violacion
// Mal ejemplo: Clase con multiples responsabilidades
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
// Responsabilidad 1: Gestion de datos del usuario
getName() { return this.name; }
setName(name) { this.name = name; }
// Responsabilidad 2: Operaciones de base de datos
save() {
db.query('INSERT INTO users ...');
}
// Responsabilidad 3: Envio de correo
sendWelcomeEmail() {
emailService.send(this.email, 'Welcome!');
}
// Responsabilidad 4: Serializacion JSON
toJSON() {
return JSON.stringify({ name: this.name, email: this.email });
}
}
Ejemplo Mejorado
// Buen ejemplo: Responsabilidades separadas
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
getName() { return this.name; }
setName(name) { this.name = name; }
}
class UserRepository {
save(user) {
db.query('INSERT INTO users ...');
}
}
class UserNotificationService {
sendWelcomeEmail(user) {
emailService.send(user.email, 'Welcome!');
}
}
class UserSerializer {
toJSON(user) {
return JSON.stringify({ name: user.name, email: user.email });
}
}
2. Principio Abierto/Cerrado (OCP)
El software debe estar abierto para extension, pero cerrado para modificacion.
Ejemplo de Violacion
// Mal ejemplo: Se requiere modificacion cada vez que se agrega un nuevo metodo de pago
class PaymentProcessor {
processPayment(type, amount) {
if (type === 'credit') {
// Procesamiento de tarjeta de credito
} else if (type === 'debit') {
// Procesamiento de tarjeta de debito
} else if (type === 'paypal') {
// Procesamiento de PayPal
}
// Para agregar un nuevo metodo de pago, hay que modificar este metodo
}
}
Ejemplo Mejorado
// Buen ejemplo: Abierto para extension
class PaymentProcessor {
constructor(paymentMethod) {
this.paymentMethod = paymentMethod;
}
processPayment(amount) {
return this.paymentMethod.process(amount);
}
}
// Cada metodo de pago es una clase independiente
class CreditCardPayment {
process(amount) { /* Procesamiento de tarjeta de credito */ }
}
class PayPalPayment {
process(amount) { /* Procesamiento de PayPal */ }
}
// Se pueden agregar nuevos metodos de pago sin cambiar el codigo existente
class CryptoPayment {
process(amount) { /* Procesamiento de criptomoneda */ }
}
// Uso
const processor = new PaymentProcessor(new CryptoPayment());
processor.processPayment(1000);
3. Principio de Sustitucion de Liskov (LSP)
Las clases derivadas deben ser sustituibles por sus clases base.
Ejemplo de Violacion
// Mal ejemplo: Un cuadrado es un tipo de rectangulo, pero su comportamiento es diferente
class Rectangle {
constructor(width, height) {
this.width = width;
this.height = height;
}
setWidth(width) { this.width = width; }
setHeight(height) { this.height = height; }
getArea() { return this.width * this.height; }
}
class Square extends Rectangle {
setWidth(width) {
this.width = width;
this.height = width; // Como es un cuadrado, tambien cambia la altura
}
setHeight(height) {
this.width = height; // Como es un cuadrado, tambien cambia el ancho
this.height = height;
}
}
// Codigo problematico
function increaseRectangleWidth(rect) {
rect.setWidth(rect.width + 1);
// Si es Rectangle: area = (width + 1) * height
// Si es Square: area = (width + 1) * (width + 1) ← Resultado inesperado
}
Ejemplo Mejorado
// Buen ejemplo: Usar una interfaz comun
class Shape {
getArea() { throw new Error('Not implemented'); }
}
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
getArea() { return this.width * this.height; }
}
class Square extends Shape {
constructor(side) {
super();
this.side = side;
}
getArea() { return this.side * this.side; }
}
4. Principio de Segregacion de Interfaces (ISP)
Los clientes no deben depender de metodos que no utilizan.
Ejemplo de Violacion
// Mal ejemplo: Interfaz gigante
class Worker {
work() { /* Trabajar */ }
eat() { /* Comer */ }
sleep() { /* Dormir */ }
}
class Robot extends Worker {
work() { /* Trabajar */ }
eat() { throw new Error('Robots do not eat'); } // Innecesario
sleep() { throw new Error('Robots do not sleep'); } // Innecesario
}
Ejemplo Mejorado
// Buen ejemplo: Interfaces separadas
class Workable {
work() { throw new Error('Not implemented'); }
}
class Eatable {
eat() { throw new Error('Not implemented'); }
}
class Sleepable {
sleep() { throw new Error('Not implemented'); }
}
class Human {
work() { /* Trabajar */ }
eat() { /* Comer */ }
sleep() { /* Dormir */ }
}
class Robot {
work() { /* Trabajar */ }
// eat() y sleep() no son necesarios
}
5. Principio de Inversion de Dependencias (DIP)
Los modulos de alto nivel no deben depender de modulos de bajo nivel. Ambos deben depender de abstracciones.
Ejemplo de Violacion
// Mal ejemplo: Dependencia directa de implementacion concreta
class UserService {
constructor() {
this.database = new MySQLDatabase(); // Dependencia de implementacion concreta
}
getUser(id) {
return this.database.query(`SELECT * FROM users WHERE id = ${id}`);
}
}
Ejemplo Mejorado
// Buen ejemplo: Dependencia de abstraccion (interfaz)
class Database {
query(sql) { throw new Error('Not implemented'); }
}
class MySQLDatabase extends Database {
query(sql) { /* Implementacion MySQL */ }
}
class PostgreSQLDatabase extends Database {
query(sql) { /* Implementacion PostgreSQL */ }
}
class UserService {
constructor(database) {
this.database = database; // Dependencia de abstraccion (inyeccion de dependencias)
}
getUser(id) {
return this.database.query(`SELECT * FROM users WHERE id = ${id}`);
}
}
// Uso
const userService = new UserService(new PostgreSQLDatabase());
// En pruebas
const mockDatabase = { query: jest.fn() };
const testService = new UserService(mockDatabase);
Practica de SOLID
Lista de Verificacion
La responsabilidad de la clase es una sola? (SRP)
Se puede agregar nueva funcionalidad sin modificar codigo existente? (OCP)
Las clases derivadas se pueden usar en lugar de las clases base? (LSP)
Las interfaces son minimas? (ISP)
Se depende de abstracciones en lugar de implementaciones concretas? (DIP)
Balance en la Aplicacion
Evitar aplicacion excesiva:
- No aplicar patrones complejos a problemas simples
- Ser consciente de YAGNI (You Ain't Gonna Need It)
- Refactorizar gradualmente segun sea necesario
Resumen
Los principios SOLID son directrices para disenar software con alta mantenibilidad y extensibilidad. No es necesario aplicar todos los principios estrictamente todo el tiempo, pero ser consciente de ellos como criterios de diseno permite escribir mejor codigo.
← Volver a la lista