En muchas ocasiones hemos visto en nuestro trabajo como nos cambiaban los requerimientos de alguno de los módulos cuando ya estaban prácticamente terminados. Lo que suponía cambiar el módulo, y comprobar que no hubiera efectos colaterales en otros módulos relacionados. Tener en cuenta este principio va a hacer que evitemos muchos de los problemas relacionados con los cambios en un módulo a causa de cambios en las especificaciones de las clases o componentes.
Por ejemplo, supongamos una clase conductor (Driver) que es capaz de conducir Vehículos (Vehicle). Si los requerimientos del sistema cambiaran y ahora el conductor tuviese que conducir sólo vehículos a motor, está claro que la clase Vehicle cambiaría en su comportamiento y posiblemente en su interfaz, por lo que la clase Driver tendría que adaptarse a los nuevos cambios del la clase Vehicle.
El Principio de Abierto-Cerrado, del inglés “The Open-Close Principle (OCP)”, nos viene a decir que cualquier entidad software (clases, módulos, funciones, etc.) debe de estar abierta para ser extendida en funcionalidad pero cerrada para ser modificada. Es decir, una clase que cumpla con OCP tiene estas dos características:
- La funcionalidad del módulo puede cambiarse o extenderse en base a los cambios que requiera el sistema.
- Extender la funcionalidad de un módulo no implica cambios en el código fuente de ese módulo en si mismo.
¿Cómo se puede entender esto? ¿Cómo cambiar el comportamiento de una clase sin cambiar su código fuente? ¿Cómo hacer que la clase Driver no se tenga que cambiar y sin embargo sea capaz de tratar con el nuevo tipo de vehículo. La respuesta está en la abstracción. La abstracción se puede conseguir de muchas maneras en programación orientada a objetos. A través de interfaces, clases abstractas, delegados, etc.
En este case, la interfaz IDriver es la abstracción que define la clase Driver para conducir un Vehicle. Fíjese en que no he llamado a la interfaz IVehicle como podría haber sido lo lógico. Esto es así porque el que define la funcionalidad es el Driver mientras que Vehicle es únicamente una implementación de esa interfaz. Para implementar el cambio que decía que un Driver sólo puede conducir vehículos a motor bastaría con cambiar únicamente la implementación de Vehicle para ajustarse a las nuevas necesidades.
Con este diseño, la clase Driver cumpliría con OCP, pues está abierto a cambios (respecto a conducir vehículos) y cerrado a los cambios (para cambiar el comportamiento de los vehículos, no se necesita cambiar). Otro tema sería que los cambios que se pidieran no fuesen soportados por la interfaz disponible. Por ejemplo, tener en cuenta vehículos voladores. En este caso los cambios serían a nivel de requerimientos de más alto nivel, por lo que se entiende que no quede más remedio que cambiar la clase Driver, la interfaz IDriver, y evidentemente todas las implementaciones de esta interfaz.
Anticipación al uso de OCP
No debemos caer en la tentación tampoco de crear abstracciones e interfaces en todas las clases. Esto haría que el diseño creara interfaces por casi cualquier referencia a otra clase que se tuviera. Y haría el código menos legible. Además, esto tampoco nos evitaría tener una clase completamente cerrada. siempre nos pueden pedir un cambio que no esté soportado por las abstracciones que tengamos. Entonces, ¿cuando debemos forzar una clase a que cumpla con este principio? Cuando nos lo diga el sentido común. Yo por ejemplo en mi caso, lo aplico siempre que vea muy claro una dependencia a una clase cuya funcionalidad veo que tenga ciertas posibilidades de que cambie de comportamiento. Otro caso donde es conveniente el uso de OCP es cuando te piden un cambio en una clase, y ese cambio afecta a las clases llamantes de la clase modificada (se arrastran los cambios). En este caso, me creo la interfaz para esas clases y hago que la clase que cambia la implemente. De esta manera, aunque la primera vez tenga que hacer la refactorización de varias clases, me aseguro de que si me piden nuevos cambios, estos sólo afecten a la clase que implemente la interfaz.