🚨 ¡Nueva review! 🔇 Los mejores cascos con ANC del mercado: los Sony WH-1000XM4 . ¡Échale un ojo! 👀

¿Qué es el principio Abierto/Cerrado o Open/Closed Principle (OCP)?

Extiende sin miedo, no toques lo que ya funciona. Diseña código flexible y extensible.

Escrito por domin el 1 de septiembre de 2024 · Actualizado el 8 de febrero de 2026

🔓 Open/Closed Principle (OCP) - Principio de Abierto/Cerrado

¿Qué es?

El Open/Closed Principle es uno de los cinco principios SOLID de la programación orientada a objetos. Este principio dice que las entidades de software (clases, módulos, funciones…) deben estar abiertas a la extensión pero cerradas a la modificación.

La frase original de Bertrand Meyer, que fue quien formuló este principio en 1988 en su libro “Object-Oriented Software Construction”, es:

“Software entities should be open for extension, but closed for modification.”

Esto quiere decir que para agregar funcionalidades nuevas deberías crear código nuevo sin tener que modificar el que ya está funcionando. Así reduces bastante la posibilidad de meter bugs al añadir cosas nuevas, porque el código existente queda intacto.

Gato negro representando el principio abierto/cerrado OCP de los principios SOLID.

¿De dónde sale este principio?

El OCP tiene su origen en Bertrand Meyer, un ingeniero de software francés que lo formuló en 1988. Meyer es conocido por ser el creador del lenguaje de programación Eiffel (no se nota que es francés) y por su enfoque en el diseño por contrato (Design by Contract). En su visión original, la extensión se conseguía principalmente a través de la herencia de clases.

Sin embargo, fue Robert C. Martin (el tío Bob) quien lo adaptó al contexto de la POO moderna cuando recopiló los principios SOLID a finales de los 90 y principios de los 2000. Uncle Bob amplió la idea y puso el foco en el uso de abstracciones (interfaces y clases abstractas) como la forma principal de cumplir este principio. En lugar de depender exclusivamente de la herencia, se apuesta por la composición y el polimorfismo.

En su libro “Agile Software Development, Principles, Patterns, and Practices” (2002), Uncle Bob lo describe como uno de los principios fundamentales para crear software que escala sin convertirse en un monstruo de spaghetti code.

¿Qué significa “abierto” y “cerrado”?

Abierto a extensión

Puedes agregar nuevas funcionalidades derivando clases, implementando interfaces o usando composición. El sistema crece sin drama.

Cerrado a modificación

No tocas el código que ya está en producción y funcionando. Lo que ya funciona, no se rompe.


¿Por qué es tan importante?

Aplicar el OCP te da ventajas muy claras en el día a día del desarrollo:

En el entorno profesional esto es importantísimo. Imagina un proyecto con 50 desarrolladores donde cada vez que alguien añade algo nuevo tiene que tocar una clase central que todo el mundo usa. Los conflictos en Git serían una pesadilla.


Ejemplo

Imagina que tenemos un carrito de la compra en la página y queremos vender un ebook. Para venderlo queremos ofrecer al usuario varias opciones de pago: Bizum, PayPal, tarjeta de crédito etc.

Tenemos la clase llamada PaymentProcessor que se va a encargar de esta tarea:

class PaymentProcessor {
    public function process($paymentMethod) {
        if ($paymentMethod === 'credit_card') {
            echo "Processing credit card payment";
        } elseif ($paymentMethod === 'paypal') {
            echo "Processing PayPal payment";
        } elseif ($paymentMethod === 'bizum') {
            echo "Processing Bizum payment";
        }
    }
}

En este código cada vez que queramos añadir un nuevo método de pago vamos a tener que abrir esta clase y meter un elseif más. ¿Que ahora quieres aceptar criptomonedas? Pues otro elseif. ¿Que mañana quieres Google Pay? Pos otro más. Esto inflige el principio de Abierto/Cerrado (OCP) porque estás modificando código existente cada vez que necesitas extender la funcionalidad.

Además, fíjate en los problemas que trae esto:

Refactor

Para respetar el OCP haremos una refactorización usando una interfaz y polimorfismo:

interface PaymentMethod {
    public function processPayment(): void;
}

class CreditCardPayment implements PaymentMethod {
    public function processPayment(): void {
        echo "Processing credit card payment";
    }
}

class BizumPayment implements PaymentMethod {
    public function processPayment(): void {
        echo "Processing Bizum payment";
    }
}

class PayPalPayment implements PaymentMethod {
    public function processPayment(): void {
        echo "Processing PayPal payment";
    }
}

class PaymentProcessor {
    public function process(PaymentMethod $paymentMethod): void {
        $paymentMethod->processPayment();
    }
}

// Ejemplo de uso
$paymentProcessor = new PaymentProcessor();

$creditCardPayment = new CreditCardPayment();
$paymentProcessor->process($creditCardPayment);

$paypalPayment = new PayPalPayment();
$paymentProcessor->process($paypalPayment);

$bizumPayment = new BizumPayment();
$paymentProcessor->process($bizumPayment);

Ahora PaymentProcessor es tontito y ni sabe ni le importa qué método de pago le llega. Solo sabe que implementa PaymentMethod y que tiene un método processPayment(). Si mañana quieres aceptar criptomonedas, solo creas una clase nueva:

class CryptoPayment implements PaymentMethod {
    public function processPayment(): void {
        echo "Processing cryptocurrency payment";
    }
}

// Usarlo es inmediato, sin tocar NADA existente
$paymentProcessor->process(new CryptoPayment());

Cero modificaciones en el código existente. Abierto a extensión, cerrado a modificación.


Un ejemplo más real: sistema de notificaciones

El ejemplo del carrito es muy didáctico, pero vamos a ver uno más cercano al código del día a día. Imagina un sistema de notificaciones que envía alertas a los usuarios:

// MAL: violando el OCP
class NotificationService {
    public function send(string $type, string $message): void {
        if ($type === 'email') {
            // Configurar SMTP, enviar email...
            echo "Enviando email: $message";
        } elseif ($type === 'sms') {
            // Conectar con API de SMS...
            echo "Enviando SMS: $message";
        } elseif ($type === 'push') {
            // Conectar con servicio de push...
            echo "Enviando push notification: $message";
        } elseif ($type === 'telegram') {
            // Conectar con Bot API de Telegram...
            echo "Enviando por Telegram: $message";
        }
    }
}

Cada canal nuevo a añadir significa que vamos a tener que tocar esta clase. Y cada canal tiene su propia lógica de conexión, configuración, manejo de errores… Todo eso metido en un solo método. Menudo drama Pepe.

El refactor aplicando OCP:

interface NotificationChannel {
    public function send(string $message): void;
}

class EmailNotification implements NotificationChannel {
    public function send(string $message): void {
        // Configurar SMTP, enviar email...
        echo "Enviando email: $message";
    }
}

class SmsNotification implements NotificationChannel {
    public function send(string $message): void {
        // Conectar con API de SMS...
        echo "Enviando SMS: $message";
    }
}

class TelegramNotification implements NotificationChannel {
    public function send(string $message): void {
        // Conectar con Bot API de Telegram...
        echo "Enviando por Telegram: $message";
    }
}

class NotificationService {
    public function __construct(
        private NotificationChannel $channel
    ) {}

    public function notify(string $message): void {
        $this->channel->send($message);
    }
}

// Uso
$emailService = new NotificationService(new EmailNotification());
$emailService->notify("Tu pedido ha sido enviado");

$telegramService = new NotificationService(new TelegramNotification());
$telegramService->notify("Nuevo login detectado");

Ahora cada canal de notificación es independiente. Si mañana quieres enviar notificaciones por paloma mensajera, solo creas PalomaNotification y listo. El NotificationService ni se entera.


Otro ejemplo: exportación de reportes

Este es un caso que te vas a encontrar seguro. Imagina que tienes que exportar reportes en distintos formatos:

// MAL: la típica clase que crece sin parar
class ReportExporter {
    public function export(array $data, string $format): string {
        if ($format === 'pdf') {
            // 50 líneas generando PDF...
            return "PDF generado";
        } elseif ($format === 'csv') {
            // 30 líneas generando CSV...
            return "CSV generado";
        } elseif ($format === 'excel') {
            // 40 líneas generando Excel...
            return "Excel generado";
        }
        // ¿HTML? ¿JSON? ¿XML? ¿Markdown?... Esto no para de crecer
    }
}

La solución es la misma idea:

interface ReportFormatter {
    public function format(array $data): string;
}

class PdfFormatter implements ReportFormatter {
    public function format(array $data): string {
        // Lógica para generar PDF...
        return "PDF generado";
    }
}

class CsvFormatter implements ReportFormatter {
    public function format(array $data): string {
        // Lógica para generar CSV...
        return "CSV generado";
    }
}

class ExcelFormatter implements ReportFormatter {
    public function format(array $data): string {
        // Lógica para generar Excel...
        return "Excel generado";
    }
}

class ReportExporter {
    public function export(array $data, ReportFormatter $formatter): string {
        return $formatter->format($data);
    }
}

// Si mañana necesitas Markdown, solo creas:
class MarkdownFormatter implements ReportFormatter {
    public function format(array $data): string {
        return "Markdown generado";
    }
}

El OCP básicamente se reduce a: depende de abstracciones (interfaces) y usa polimorfismo. Así cada vez que necesites algo nuevo, creas una clase nueva que implemente la interfaz y el código existente ni se entera.


Relación con los patrones de diseño

El OCP está muy ligado a varios patrones de diseño que seguramente ya conozcas o te suenen. Estos patrones son herramientas que te ayudan a cumplir el principio de forma natural:

Strategy

Encapsula algoritmos intercambiables. Justo lo que hemos visto con PaymentMethod: cada estrategia de pago es independiente.

Factory

Delega la creación de objetos a fábricas. Puedes añadir nuevos tipos de objetos sin modificar el código que los consume.

Decorator

Añade funcionalidades envolviendo objetos existentes. Extiendes sin modificar la clase original.

Observer

Permite suscribir nuevos listeners sin modificar el emisor de eventos. Extensión pura.

Template Method

Define el esqueleto de un algoritmo y deja que las subclases redefinan los pasos. La estructura no cambia, solo se extiende.

State

Cada estado es una clase independiente. Añadir un estado nuevo no requiere tocar los existentes.

Si te fijas, todos estos patrones comparten la misma filosofía: nuevas funcionalidades = nuevas clases, no modificaciones en las existentes.


¿Cómo detectar que estás violando el OCP?

Para detectar incumplimientos del principio Open/Closed puedes fijarte en estas señales de alarma:

Uso excesivo de condicionales (if, else if, switch…)

Es la señal más clara. Si tienes una estructura con un if seguido de muchos elseif o un switch con 10 cases, es muy probable que estés violando el OCP. Cada vez que añadas una opción nueva vas a tener que abrir esa estructura y meter un caso más.

La clase sufre muchas modificaciones

Si en el historial de Git ves que una clase se modifica constantemente y por razones diferentes (hoy para añadir un método de pago, mañana para un formato de exportación), es una señal clara.

Clases con múltiples tipos de comportamiento

Por ejemplo, una clase que exporta reportes en varios formatos en un solo método (PDF, CSV, HTML, Markdown…) o una clase que maneja distintos tipos de usuarios con lógica diferente según el tipo.

Dependencias de tipos concretos en lugar de abstractos

Si tu código depende directamente de BizumPayment en lugar de PaymentMethod, estás acoplado a una implementación concreta. Cuando necesites cambiar o añadir algo, tendrás que modificar ese código.

Incumplimiento del Principio de Sustitución de Liskov (LSP)

El OCP y el LSP están muy relacionados. Si no puedes sustituir una clase hija por su clase base sin que las cosas se rompan, es bastante probable que también estés violando el OCP. Ambos principios se refuerzan mutuamente.

Si es complicado agregar funcionalidades sin modificar código existente

Esta es la prueba definitiva. Si cada vez que necesitas algo nuevo tienes que tocar código que ya funciona, algo no está bien diseñado. Lo ideal es que una funcionalidad nueva signifique crear código, no modificar código.

No se usan patrones de diseño adecuados

No usar los patrones Strategy, State, Factory o Decorator cuando la situación lo pide puede ser una pista de que no se está respetando el OCP. Estos patrones son herramientas diseñadas precisamente para añadir funcionalidades sin tocar lo existente.


Errores comunes al aplicar el OCP

Como con todos los principios SOLID, aplicar el OCP también tiene sus trampas. Aquí van los errores más habituales:


¿Cuándo NO aplicar el OCP?

Igual que con el resto de principios SOLID, el OCP no es una ley universal. Hay situaciones donde forzarlo es peor que el problema:

Recuerda: El OCP es una herramienta, no una religión. Aplícalo cuando aporte valor real, no por cumplir un checklist.


Relación con otros principios SOLID

El OCP no vive solo. Está conectado con el resto de principios SOLID y se refuerzan mutuamente:

Es muy difícil cumplir el OCP si no cumples los demás. Por eso los principios SOLID funcionan como un equipo: cada uno aporta su parte para que el conjunto sea sólido (nunca mejor dicho).


Conclusión

El Principio Abierto/Cerrado es uno de los pilares más potentes de SOLID. Su idea central es sencilla pero poderosa: diseña tu código de forma que puedas añadir cosas nuevas sin tocar lo que ya funciona.

La clave técnica es usar abstracciones (interfaces, clases abstractas) y polimorfismo para que el código existente trabaje con contratos, no con implementaciones concretas. Así, cada extensión es una clase nueva que cumple un contrato, sin necesidad de modificar nada.

No te obsesiones con aplicarlo desde el primer momento. Deja que el código te pida la abstracción cuando la necesite.

Espero que se haya entendido ejeejjeje EA nos beermos! 🍻


Pon a prueba lo aprendido

1. ¿Quién formuló originalmente el Principio Abierto/Cerrado?

2. ¿Qué significa que una clase esté 'abierta a extensión pero cerrada a modificación'?

3. ¿Cuál es la señal más evidente de que estás violando el OCP?

4. ¿Qué mecanismo técnico es clave para cumplir el OCP?

5. ¿Cuál de estos patrones de diseño NO está directamente relacionado con el OCP?

6. ¿Qué principio SOLID está más ligado al OCP para que la extensión funcione correctamente?

7. ¿Cuál es un error común al aplicar el OCP?

8. ¿En cuál de estas situaciones NO tiene sentido aplicar el OCP?