El Principio de Abierto/Cerrado consiste en añadir comportamiento sin modificar el código existente.
Llegamos a la segunda letra de SOLID, la O: el Principio de Abierto/Cerrado (Open/Closed Principle u OCP).
Este principio, formulado originalmente por Bertrand Meyer en 1988, plantea una aparente paradoja que puede confundir al principio. Su definición dice:
“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.” (Las entidades de software deben estar abiertas para su extensión, pero cerradas para su modificación).
¿Abierto y cerrado a la vez? Aunque parezca contradictorio, el principio dice que deberíamos poder añadir funcionalidades sin tocar el código original.
- Abierto para extensión: Podemos añadir nuevos comportamientos o funcionalidades.
- Cerrado para modificación: No necesitamos alterar el código que ya está escrito, probado y funcionando cada vez que añadimos un comportamiento.
Cada vez que modificamos una clase existente, corremos el riesgo de introducir nuevos errores en funcionalidades que antes iban bien. El OCP trata de minimizar ese riesgo.
El síntoma: el switch infinito
La forma más fácil de detectar que estás violando el OCP es cuando ves una clase llena de if/else o switch que comprueban el tipo de un objeto para decidir qué hacer.
Supongamos que tenemos una clase ProcesadorDePagos. Al principio, solo aceptábamos tarjeta de crédito. Luego el jefe quiso PayPal. Y luego Bitcoin.
El mal diseño
public enum TipoPago { Tarjeta, Paypal, Bitcoin }
public class ProcesadorDePagos
{
public void Procesar(TipoPago tipo, double cantidad)
{
if (tipo == TipoPago.Tarjeta)
{
// Lógica para cobrar con tarjeta...
Console.WriteLine("Cobrando con Tarjeta...");
}
else if (tipo == TipoPago.Paypal)
{
// Lógica para conectar con API de Paypal...
Console.WriteLine("Redirigiendo a Paypal...");
}
else if (tipo == TipoPago.Bitcoin)
{
// Lógica de wallet...
Console.WriteLine("Transacción en Blockchain...");
}
}
}
¿Cuál es el problema aquí? Si mañana queremos añadir “Bizum” o “Stripe”, tenemos que:
- Modificar el
enum. - Abrir y modificar la clase
ProcesadorDePagos. - Añadir un nuevo
else if. - Recompilar y volver a testear toda la clase (asegurándonos de no haber roto el pago con Tarjeta por error).
Esta clase no es cerrada a modificación. Cada nueva feature implica cirugía a corazón abierto.
La solución: polimorfismo
Para cumplir con el OCP, utilizamos uno de los pilares de la POO: el polimorfismo (generalmente a través de interfaces o clases abstractas).
En lugar de que el ProcesadorDePagos pregunte “¿qué eres?”, simplemente le diremos al objeto de pago: “Procesa el pago”.
Creamos una interfaz (o clase abstracta) que defina el contrato.
public interface IMetodoPago
{
void ProcesarPago(double cantidad);
}
Cada forma de pago es ahora una clase separada. Esto también ayuda al SRP (cada clase se ocupa de su lógica).
public class PagoTarjeta : IMetodoPago
{
public void ProcesarPago(double cantidad)
{
Console.WriteLine("Cobrando con Tarjeta...");
}
}
public class PagoPaypal : IMetodoPago
{
public void ProcesarPago(double cantidad)
{
Console.WriteLine("Redirigiendo a Paypal...");
}
}
Ahora reescribimos nuestro procesador. Fíjate en el cambio:
public class ProcesadorDePagos
{
public void Procesar(IMetodoPago metodo, double cantidad)
{
// El procesador NO sabe qué tipo de pago es.
// Solo sabe que cumple la interfaz.
metodo.ProcesarPago(cantidad);
}
}
¿Qué hemos conseguido?
Ahora, si queremos añadir Bizum, solo necesitamos lo siguiente:
- Creamos una nueva clase
PagoBizum : IMetodoPago. - Implementamos su lógica.
- ¡Y ya está!
No tenemos que tocar ni una sola línea de ProcesadorDePagos.
- El sistema está Abierto a extensión: Hemos añadido Bizum.
- El sistema está Cerrado a modificación:
ProcesadorDePagosno ha cambiado.
// Ejemplo de uso
var procesador = new ProcesadorDePagos();
// Podemos pasarle cualquier cosa que sea IMetodoPago
procesador.Procesar(new PagoTarjeta(), 100);
procesador.Procesar(new PagoBizum(), 50); // ¡Funciona sin tocar el procesador!
Advertencia: no te obsesiones
Como todo en ingeniería, el OCP no es gratis. Introduce complejidad.
Si aplicamos OCP a todo, acabaremos con millones de interfaces pequeñas y una “explosión de clases”.
Aplica OCP solo en las partes de tu sistema que son propensas a cambiar con frecuencia.
Si una parte del código no ha cambiado en dos años, no necesitas refactorizarla para que sea Open/Closed. No intentes predecir el futuro: aplica OCP cuando veas que el cambio es real.