S.O.L.I.D. Principios de Diseño Orientados a Objeto

Descripción general

Todos conocemos los beneficios de hacer desarrollo software con un nivel bajo de acoplamiento, alta cohesión y una fuerte encapsulación. Cuando nos encontramos con proyectos de pequeño tamaño, estos conceptos más o menos los podemos ir controlando. Pero cuando trabajamos en equipos donde se abarcan proyectos de gran envergadura, es complicado. Sobre todo si los miembros del grupo no tienen claros estos conceptos.

[TOC] Tabla de Contenidos


↑↑↑

S.O.L.I.D. Principios de Diseños Orientados a Objeto

En la entrada anterior hablaba de los beneficios de hacer desarrollo software con un nivel bajo de acoplamiento, alta cohesión y una fuerte encapsulación. Cuando nos encontramos con proyectos de pequeño tamaño, estos conceptos más o menos los podemos ir controlando. Pero cuando trabajamos en equipos donde se abarcan proyectos de gran envergadura, es complicado. Sobre todo si los miembros del grupo no tienen claros estos conceptos.

En los años 90, Robert C. Martin, hace una recopilación de principios a modo de vía para conseguir que estos desarrollos empresariales alcancen un bajo nivel de acoplamiento, alta cohesión y una fuerte encapsulación. Estos principios se englobaron dentro de las siglas S.O.L.I.D.

image

El Principio de Responsabilidad Única (SRP) nos dice que una clase sólo debe tener una única razón para cambiar. Este principio nos ayuda a dirigir la cohesión y controlar el acoplamiento.

El Principio de Abierto-Cerrado (OCP) nos indica cómo diseñar clases de manera que estas se puedan extender sin tener que modificar su funcionalidad principal. Aplicando este principio conseguimos sistemas bien encapsulados y altamente cohesivos.

El Principio de Substitución de Liskov (LSP) se centra en los conceptos de encapsulación y cohesión. Y dice que una clase que implemente una interfaz o herede de una clase base no debe violar la intención o la semántica de la abstracción heredada.

El Principio de Segregación de la Interfaz (ISP) nos habla de como las clases no deben exponer interfaces que las aplicaciones que las usan no necesitan. Aplicando este principio se obtiene una mejora en la encapsulación y cohesión de sistemas.

El Principio de Inversión de Dependencias (DIP) está enfocado en hacernos entender de como los detalles de implementación debe depender de la funcionalidad de alto nivel que requiere el sistema y no al revés. Y de como conseguir invertir esta relación de manera satisfactoria.

Aunque todos los principios SOLID están relacionados entre sí.Voy a dedicar una entrada a profundizar sobre cada uno de estos dando ejemplos muy básicos y explicativos que ayuden a entender la manera en que pueden ser usados en nuestros desarrollos.


↑↑↑

El principio de la Responsabilidad Única (SRP)

El Principio de Responsabilidad Única (o en inglés Single Responsibility Principle (SRP)) fue descrito por Tom DeMarco y Meilir Page-Jones en un trabajo que llamado “Cohesión” en el que se definen las relaciones de los elementos de un módulo. Este principio nos viene a decir que una clase sólo debería tener una única razón para cambiar.

“Una clase debe tener una única razón para cambiar.”

Lo que trata de decirnos este principio es que debemos huir de aquellas clases monolíticas que aglutinen varias responsabilidades. Pero, ¿qué es una responsabilidad? Desde el punto de vista de SRP se podría decir que una responsabilidad en una clase es una razón para cambiar esa clase. Es decir, si encontramos que hay más de una razón por la que una clase pueda cambiar entonces es que esa clase tiene más de una responsabilidad.

¿Y no sería más sencillo decir que una clase debería tener una sola razón para existir en lugar de para cambiar? Cuidado, porque esto nos podría llevar a hacer muy malos diseños de sistemas. Llevado al pie de la letra podría encontrarme con cientos de clases en mi sistema, cada una con una única función. Lo que haría al sistema nada mantenible.

El punto clave que nos dice las razones por la que una clase puede cambiar va a depender del contexto en el que se va a dar uso a esa clase. Pongamos por ejemplo una clase que represente al motor de un coche. ¿Necesitamos conocer el régimen de revoluciones del motor?, ¿el peso?, ¿número de cilindros?, ¿presión del inyector de gasolina?, ¿o lo que nos interesa es simplemente poder arrancarlo y esperar que haga andar a un coche para llevaros de un sitio a otro? La respuesta a estas preguntas va a depender del contexto en el cual usemos la clase motor. No va a tener las mismas necesidades sobre esta clase un fabricante de coches que un usuario que usa el coche para ir de un sitio a otro. El fabricante de coches va a notar un número mayor de responsabilidades en el motor que el usuario del coche. Por tanto, para el fabricante, este principio recomendaría dividir la clase motor en otras más pequeñas que cumplan con las especificaciones de manera individual.

Veamos otro ejemplo típico de violación del SRP: Supongamos que tenemos la clase Employee (Empleado) en un sistema de gestión de una empresa cualquiera. Esta clase nos permite realizar las tareas esperadas sobre un empleado: Cargor y almacenarlo en una base de datos, generar la nómina, información básica del empleado, etc.

image

Ahora supongamos dos aplicaciones que hacen uso de la clase Employee. Una para ser usada por el departamento de recursos humanos para la gestión de las nominas del personal y otra para la gestión de los proyectos que lleva la empresa.

¿Podemos pensar que no se está siguiendo el SRP? Lo que sería lo mismo, ¿creemos que la clase Employee tiene más de una razón por la que pueda cambiar? A mi se me ocurren unas cuantas: Cambiar el formato de almacenamiento de base de datos, modificar los campos que definen a un empleado, cambiar la lógica de generación de nóminas, etc. Es decir, esta clase tiene varias responsabilidades: Es responsable de la persistencia de los clientes, responsable de caracterizar a un empleado, responsable de generar las nóminas, etc.

Las consecuencias de violar el SRP en este caso son dos:

Una mejora en el diseño sería separar las responsabilidades en clases distintas

image

Hemos creado dos nuevas clases: una para la gestión de nóminas y otra para el almacenamiento en la base de datos. Hay que fijarse en el detalle de que la clase Employee no depende de las nuevas clases EmployeeAccount y de EmployeeStorage, sino que la dependencia la tienen las aplicaciones.

En definitiva. Este principio es uno de los más simples de SOLID, y sin embargo de los más difíciles de implementar correctamente. Aplicando SRP,podemos alcanzar niveles más bajos de acoplamiento y una cohesión más alta del sistema.


↑↑↑

El Principio Abierto-Cerrado (OCP)

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.

image

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:

  1. La funcionalidad del módulo puede cambiarse o extenderse en base a los cambios que requiera el sistema.
  2. 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.

image

En este caso, 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.


↑↑↑

Referencia Bibliográfica

Este artículo me ha parecido tan interesante que lo he copiado tal cual 
para tener una copia local del mismo

The Art of the Left Foot 
El blog de Oscar Arrivi 

sábado, 29 de mayo de 2010
S.O.L.I.D. Principios de Diseños Orientados a Objeto (parte 1) 

    http://theartoftheleftfoot.blogspot.com.es/2010/05/solid-principios-de-disenos-orientados.html

viernes, 4 de junio de 2010 
S.O.L.I.D. – El principio de la Responsabilidad Única (SRP) (parte 2) 
http://theartoftheleftfoot.blogspot.com.es/2010/06/solid-el-principio-de-la.html

martes, 8 de junio de 2010
S.O.L.I.D. – El Principio Abierto-Cerrado (OCP) (parte 3)
http://theartoftheleftfoot.blogspot.com.es/2010/06/solid-el-principio-abierto-cerrado-ocp.html

↑↑↑

A.2.Enlaces

[Para saber mas]
[Grupo de documentos]
[Documento Index]
[Documento Start]
[Imprimir el Documento]