OCP: El Principio Abierto/Cerrado
Los artefactos de software deben estar abiertos para extensión pero cerrados para modificación — añadir nuevo comportamiento debe añadir código, no cambiarlo.
Por qué importa
El OCP, formulado originalmente por Bertrand Meyer, está en el corazón del pensamiento arquitectónico. Martin lo ilustra con un sistema de informes financieros que debe migrar de una vista web a una vista de impresión. Si el sistema está estructurado de modo que las responsabilidades están separadas y las dependencias fluyen hacia la política de alto nivel, el componente de cálculo financiero nunca necesita cambiar — solo se agrega un nuevo componente de renderizado.
La clave es la protección: los componentes de alto nivel deben protegerse de los cambios en los de bajo nivel. Cuando agregas una nueva funcionalidad añadiendo una nueva clase en lugar de modificar una existente, proteges todas las pruebas existentes y todo el comportamiento existente. La sentencia if/switch que crece con cada nuevo caso es el enemigo más común del OCP — cada nuevo caso es una modificación del código existente, probado y desplegado.
✗El problema
Cada nuevo formato de salida requiere editar la misma función — modificando código probado y desplegado, arriesgando regresiones.
Bad
def generate_report(data: list, output_type: str) -> str:
if output_type == "web":
return "<html>" + render_html(data) + "</html>"
elif output_type == "pdf":
return render_pdf(data)
# Adding "excel" means touching existing, tested code — violation!
function generateReport(data: Row[], outputType: string): string {
if (outputType === "web") {
return "<html>" + renderHtml(data) + "</html>";
} else if (outputType === "pdf") {
return renderPdf(data);
}
throw new Error("Unknown output type");
// Adding "excel" means editing here — closed to extension!
}
✓La solución
An abstract renderer interface lets each format live in its own class. Adding Excel support means writing a new class — existing code is never touched.
Good
from abc import ABC, abstractmethod
class ReportRenderer(ABC):
@abstractmethod
def render(self, data: list) -> str: ...
class WebRenderer(ReportRenderer):
def render(self, data: list) -> str:
return "<html>" + render_html(data) + "</html>"
class PDFRenderer(ReportRenderer):
def render(self, data: list) -> str:
return render_pdf(data)
class ExcelRenderer(ReportRenderer): # added without touching any existing code
def render(self, data: list) -> str:
return render_excel(data)
def generate_report(data: list, renderer: ReportRenderer) -> str:
return renderer.render(data)
interface ReportRenderer {
render(data: Row[]): string;
}
class WebRenderer implements ReportRenderer {
render(data: Row[]): string {
return "<html>" + renderHtml(data) + "</html>";
}
}
class PDFRenderer implements ReportRenderer {
render(data: Row[]): string { return renderPdf(data); }
}
class ExcelRenderer implements ReportRenderer { // new class, zero edits elsewhere
render(data: Row[]): string { return renderExcel(data); }
}
function generateReport(data: Row[], renderer: ReportRenderer): string {
return renderer.render(data);
}
💡Conclusión clave
Cada nuevo caso añadido a un if/switch es una modificación del código existente — un riesgo de regresión. El OCP dice: diseña de modo que el nuevo comportamiento llegue como código nuevo, nunca como ediciones a código que ya funciona.
🔧 Algunos ejercicios pueden tener errores. Si algo parece incorrecto, usa el botón Feedback (abajo a la derecha) para reportarlo — nos ayuda a corregirlo rápido.
Pista: Cada vez que añades un nuevo caso a un if/switch, estás violando el OCP. Una nueva clase es más segura.
✗ Tu versión