DEV Community

Miguel Ángel Sánchez Chordi
Miguel Ángel Sánchez Chordi

Posted on

Principio Abierto/Cerrado (OCP)

Siguiendo con la serie de principios SOLID, hoy hablaremos sobre el Principio Abierto/Cerrado, al que nos referiremos desde ahora como OCP.

Este principio fue enunciado por primera vez por Bertrand Meyer en su obra de 1988 Object-Oriented Software Construction y muy posteriormente, en 2002, Bob Martin publicó un articulo sobre el mismo tema.

El OCP, si es la primera vez que lo oyes enunciar, puede parecer una contradicción en si mismo, pero en breve veremos que la programación orientada a objetos nos da todas las herramientas para que este sea posible.

El principio OCP se define como:

Una clase debería estar abierta a extensión pero cerrada a modificación. Tenemos que ser capaces de extender el comportamiento de nuestras clases sin necesidad de modificar su código.

En pocas palabras, nuestras clases deberían de ser capaces de hacer más cosas, pero sin editar su código.

¿Cómo cumplimos con el OCP?

Como antes decía, puede parecer una contradicción en si mismo, pero no olvidemos que tenemos que pensar en un entorno de OOP, y debemos utilizar todos los mecanismos a nuestro alcance para conseguir esto, así que para solucionar el OCP en nuestro código debemos de usar el polimorfismo, y tenemos dos opciones igualmente válidas para implementarlo:

  • Polimorfismo con Interfaces

  • Polimorfismo con Clases Abstractas

Supongamos una clase IdentityNumberValidator, la cual tiene un método validate(IdentityDocument $document) encargada de decirnos si el número de identidad de un documento es válido o no:

Como podemos ver, la clase IdentityNumberValidator no cumple con el OCP: con cada nuevo tipo de documento que vaya a soportar nuestro sistema, esta clase debe de ser editada para dar soporte al mismo.

Esto es ciertamente un problema, y ese if parece que va a crecer como la espuma… Veamos una solución usando polimorfismo con clases abstractas:

Como se puede observar:

  • La clase IdentityDocument ahora es abstracta, y define un nuevo método isValid que es quien ejecutará la validación del número de identificación.

  • Las clases SpanishIdentityDocument y FrenchIdentityDocument se ven forzadas a implementar el método isValid.

  • La clase IdentityNumberValidator ahora únicamente llama al método isValid que es seguro que va a estar implementado, ya que la clase abstracta fuerza a ello. Por más tipos que se añadan a nuestro sistema, seguirá siendo capaz de validarlos, por tanto cumple con el OCP.

  • [Punto extra] Lanzar la excepción InvalidDocumentTypeException ya no va a ser necesario ;) Ahora carece de sentido ya que no necesitamos comprobar el tipo de documento y ver si tenemos una validación asociada a el tipo.

Como parte negativa a esta solución, puede que cumplir con el OCP esté haciendo que no cumplamos con el SRP, ya que ahora las clases concretas de cada tipo de documento tienen una responsabilidad extra: saber cómo debe ser validado su número.

Veamos ahora una solución basada en interfaces que evite esto:

¿Qué ha cambiado?

  • La lógica de la validación ahora no esta ni en el validador ni en los ValueObjects, está en un nuevo tipo de clases que llamaremos ValidatorRule y que implementan una interfaz común IdentityNumberValidatorRuleInterface.

  • Ahora el validador necesita que le pasemos en su construcción una serie de reglas de validación, y él se encargará de buscar la regla adecuada o lanzar una excepción si se da el caso de que no existe ninguna.

Ventajas

  • La lógica está mas repartida, se cumple el SRP

  • El validador es más extensible, podemos instanciarlo con las reglas que queramos cada vez, pudiendo crear infinitas combinaciones, por lo tanto se cumple el OCP.

Inconvenientes

  • La instanciación del servicio de validación es más compleja, hay que pasarle los validadores manualmente al constructor. No obstante, el uso del patrones como Abstract Factory, Factory Method, Static Factory o Simple Factory (por mencionar algunos) pueden aliviar esta carga.

  • Otra forma de añadir reglas de validación sería mediante un método addRule(IdentityNumberValidatorRuleInterface $rule), pero esto crearía un acoplamiento temporal en nuestro código, ya que hasta que no se haga una llamada a addRule nuestro validador no sirve de nada.

¿A qué huele una clase que no cumple con el OCP?

Ahora que ya sabemos qué es el OCP y como cumplir con él… ¿Cómo detectamos que nuestras clases no cumplen con él?

Debemos fijarnos siempre en:

  • Qué clases modificamos más a menudo.

  • Si cada vez que hay un nuevo requisito o una modificación de los existentes, las mismas clases se ven afectadas.

  • Cada vez que añadimos un nuevo tipo de algo (moneda, idioma, método de pago…) a nuestro sistema, las mismas clases se ven afectadas.

Aunque no son motivos determinantes, nos pueden dar una pista de que algo está pasando en nuestro código.

Hasta la próxima!

Top comments (0)