Logotipo SOLID

En esta segunda parte de los principio SOLID, vamos a ver el llamado principio abierto/cerrado (open/closed). Este principio fue formulado por uno de los grandes Bertrand Meyer, en su libro Object-Oriented Software Construction. Si nos vamos a la definición que hay dada sería:

Las entidades de software (clases, módulos, funciones, etc.) deben estar abiertas para su extensión, pero cerradas para su modificación.

Bertrand Meyer

Cuando un pequeño cambio en una aplicación, conlleva una serie de cambios en cascada, en muchos módulos interdependientes que, en principio, no deberían ser modificados, esto nos da una pista de que ese programa tiene algún problema de diseño. Esto provoca que cada cambio se convierta en una odisea, debido a que los componentes están demasiado acoplados entre sí. Cuanto mayor sea el proyecto, más y más complejo se volverá el introducir cambios si no se toman las medidas adecuadas.

El principio abierto/cerrado viene a decirnos que desarrollemos elementos (no se restringe sólo a las clases), que una vez hagan todo lo que tienen que hacer, no deban ser modificados por cambios futuros. Esto te evita, posteriormente, modificar código que está funcionando correctamente, ahorrando la posible introducción de errores en dichos cambios.

Vamos a partir del siguiente código en TypeScript. En principio el lenguaje dará igual, ya que los conceptos son genéricos, pero quienes vengáis de Java, C++, C# podréis ver que es muy parecido:

enum OnlinePlatforms {
  NETFLIX,
  PRIME_VIDEO,
}

class OnlineVideoPlayer {
  public async autoPlay(platformType: OnlinePlatforms, videoId: string): Promise<void> {
    try {
      if (platformType === OnlinePlatforms.NETFLIX) {
        await this.loadNetflixVideo(videoId);
        this.playNetflixVideo(videoId);
      } else if (platformType === OnlinePlatforms.PRIME_VIDEO) {
        await this.loadPrimeVideo(videoId);
        this.playPrimeVideo(videoId);
      }
    } catch (exception: Error) {
      // Controlar el error en la carga
    }
  }
}

Ese código tan horrible que tenemos por encima de este texto, tiene un problema grave. Si el día de mañana añadimos HBO como plataforma a usar, tocaría modificar la clase OnlineVideoPlayer para añadir la nueva funcionalidad. Aunque se podría mejorar de otras formas, en este caso, nos vamos a centrar en la mejora para un el principio abierto/cerrado.

Como aclaración para quien no conozca la sentencia await en JavaScript, permite operar con un código asíncrono de forma síncrona. Es decir, el código se queda esperando en el await hasta que termina la parte asíncrona. Posteriormente, o pasa a la siguiente sentencia si terminó correctamente, o salta a la cláusula catch si terminó con error. Si queréis que lo comente en profundidad en algún artículo avisadme.


Lo primero será sacar la funcionalidad de reproducción a distintas clases, una por plataforma. Éstas partirán de una interfaz común (dependiendo del caso, también podría ser una clase abstracta):

interface IOnlinePlayer {
  load(videoId: string): Promise;
  play(): void;
}

class NetflixPlayer implements IOnlinePlayer {}
class PrimeVideoPlayer implements IOnlinePlayer {}
Diagrama con la generalización de las clases NetflixPlayer y PrimeVideoPlayer.

Lo siguiente será modificar nuestra clase OnlineVideoPlayer, para que se quede lo más genérica posible:

class OnlineVideoPlayer {
  public async autoPlay(player: IOnlinePlayer, videoId: string): Promise<void> {
    try {
      await player.load(videoId);
      player.play();
    } catch (exception: Error) {
      // Controlar el error en la carga
    }
  }
}

Con este primer ataque, la clase OnlineVideoPlayer ya no tendría cambios a futuro. Ya que para añadir HBO al tandem lo que haremos es crear un nuevo player:

class HBOPlayer implements IOnlinePlayer {}

Como dije anteriormente, otra posible solución sería usando una clase abstracta de la que hereden nuestras implementaciones. Pero este caso vendrá condicionado normalmente por los requisitos.

¿Y cuál es la principal ventaja de todo esto? En nuestro ejemplo, la clase OnlineVideoPlayer no tiene mucho código, pero nos sirve para entender el concepto. Si le añadimos tests a todos los niveles, sabemos que funciona, etc. ¿Por qué deberíamos tener que cambiarla si añadimos otro tipo de plataforma? ¿Esto no podría hacer que se nos olvidara por ejemplo hacer alguno de los pasos que requiere cada plataforma? ¿Cada vez añada una plataforma tengo que modificar los tests unitarios de la clase? La reflexión a estas tres últimas preguntas, despejará la solución a la primera, y es que deberíamos evitar tocar código que funciona, ya que cuanto menos código funcional cambiemos, menos errores se podrán introducir.

Todo esto no impide que a futuro tengamos que hacer cambios en la clase, pero éstos, normalmente, deberían realizarse para solventar bugs que puedan surgir, y no para añadir funcionalidad de la que la clase no debería ser responsable.


Aquí terminamos la explicación del segundo principio SOLID. Como siempre, cualquier duda, sugerencia o corrección, será bienvenida. Nos vemos en el capítulo L (seguro que a quien sea fan de Death Note le ha hecho ilusión el nombre).

Puedes ver un resumen en esta entrada de Instagram.

Más artículos de esta serie:
Capítulo S: Single Responsibility Principle
Capítulo L: Liskov Substitution Principle
Capítulo I: Interface-Segregation Principle
Capítulo D: Dependency Inversion Principle

Comparte este artículo con quien quieras
Principios SOLID. Capítulo S: Single Responsibility Principle
Principios SOLID. Capítulo L: Liskov Substitution Principle

Leave a Comment

Your email address will not be published. Required fields are marked *

El tiempo límite ha expirado. Por favor, recarga el CAPTCHA.

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.