Principio Abierto-Cerrado (OCP)

El principio abierto-cerrado u Open-Closed Principle (también conocido como OCP) es uno de los cinco principios SOLID que ayuda a modelar el código orientado a objetos de una manera más robusta y mantenible a la larga. El principio OCP dice que una clase debe estar abierta para su extensión, pero cerrada para su modificación. O sea, que una vez una clase ha sido desarrollada, cualquier futura extensión que sirva para cumplir un nuevo requisito o cambio en la aplicación debe ser desarrollado fuera de esa clase. Este es uno de los principios más confusos de comprender debido al nombre tan inteligente que tiene, pero tiene su mecánica.

El principio abierto o cerrado, conocido como Open Close Principle dentro del conjunto de principios SOLID, es fundamental para diseñar código que sea mantenible y escalable. Este principio nos indica que una clase o módulo debe estar abierta para su extensión, pero cerrada para su modificación. Aunque la definición puede parecer un poco abstracta o confusa al principio, entenderla bien nos ayuda a evitar problemas comunes en el desarrollo de software, como la proliferación de condicionales que complican el mantenimiento y la evolución del código.

Cuando estamos desarrollando una clase, es natural que vayamos añadiendo métodos y atributos para cumplir con los requisitos que surgen. Sin embargo, el principio abierto o cerrado nos invita a pensar que, una vez que la clase está terminada y cumple con su función, no deberíamos modificarla directamente para añadir nuevas funcionalidades. En cambio, deberíamos extender su comportamiento sin alterar su código original. Esto implica que la clase está cerrada para modificaciones, pero abierta para ser extendida mediante mecanismos como la herencia o la implementación de interfaces.

Para ilustrar esto, imaginemos que estamos construyendo una aplicación web con un panel de control que muestra diferentes widgets: tablas con ventas, gráficas de proporciones o listas de mensajes. Tenemos una clase Dashboard que contiene una lista de widgets y un método paint que recorre cada widget y lo pinta en pantalla. Supongamos que surge la necesidad de mostrar un widget especial solo para usuarios con un nivel específico, por ejemplo, aquellos que pertenecen a un cliente con un contrato especial.

Una solución común y poco recomendable sería añadir un condicional dentro del método paint que verifique el nivel del usuario y decida si pintar o no ese widget especial. Esto podría implicar agregar flags o propiedades especiales a los widgets y condicionales que crecen con el tiempo, haciendo que el método paint se llene de ifs y se convierta en un código difícil de mantener y extender.

El principio abierto o cerrado nos dice que esta no es la forma correcta de proceder. En lugar de modificar la clase Dashboard para añadir estas condiciones, deberíamos crear nuevas clases que extiendan su funcionalidad. Por ejemplo, podríamos tener una clase base Dashboard que se encargue de pintar los widgets genéricos, y luego subclases como VIPDashboard o ExecutiveDashboard que implementen la lógica específica para esos casos especiales. De esta manera, cuando necesitemos añadir un nuevo tipo de dashboard, simplemente creamos una nueva subclase sin tocar el código original.

Para gestionar qué tipo de dashboard se debe pintar según el usuario, podríamos introducir una clase adicional, digamos DashboardPainter, que se encargue de decidir qué instancia de dashboard utilizar. Esta clase podría consultar las propiedades del usuario y devolver la instancia adecuada, delegando la responsabilidad de pintar al dashboard correspondiente.

Un ejemplo simplificado en código podría ser:

interface Dashboard {
    void paint();
}

class GeneralDashboard implements Dashboard {
    public void paint() {
        // Pintar widgets genéricos
    }
}

class VIPDashboard implements Dashboard {
    public void paint() {
        // Pintar widgets especiales para clientes VIP
    }
}

class DashboardPainter {
    public Dashboard getDashboardForUser(User user) {
        if (user.getLevel() == 4) {
            return new VIPDashboard();
        } else {
            return new GeneralDashboard();
        }
    }
}

Con esta estructura, si en el futuro aparece otro tipo de dashboard, simplemente añadimos una nueva clase que implemente Dashboard y modificamos DashboardPainter para devolver la instancia adecuada, sin alterar las clases existentes.

Es importante tener en cuenta que aplicar el principio abierto o cerrado requiere planificar desde el diseño inicial. Intentar aplicarlo cuando el código ya está lleno de condicionales y modificaciones puede ser complicado y requerir una refactorización profunda. Además, debemos cuidar no violar otros principios, como el de responsabilidad única, y preferir técnicas como la delegación o el uso de interfaces en lugar de abusar de la herencia directa.

En definitiva, el principio abierto o cerrado nos invita a diseñar sistemas que puedan crecer y adaptarse sin necesidad de modificar el código existente, favoreciendo la extensión limpia y evitando que el código se convierta en un conjunto de condicionales difíciles de mantener. Esto nos ayuda a construir software más robusto y preparado para el futuro.

Lista de reproducción
  1. 1
    Principio de Responsabilidad Única (SRP)
    8 minutos
  2. 2
    Principio Abierto-Cerrado (OCP)
    11 minutos
  3. 3
    Principio de Sustitución de Liskov (LSP)
    8 minutos
  4. 4
    Principio de Segregación de Interfaz (ISP)
    6 minutos
  5. 5
    Principio de Inversión de Dependencia (DIP)
    10 minutos