SRP: El principio de responsabilidad individual Nunca debe haber más de una razón para que una clase cambie. Considere un juego de bolos. Para su desarrollo una clase Game tiene que manejar dos responsabilidades separadas. Estos son el seguimiento del frame actual y el cálculo de la puntuación. Para lograr esto, estas dos responsabilidades tienen que ser separadas en dos clases. La clase Game queda con la responsabilidad de realizar un seguimiento de los frames del juego, y la clase Scorer tiene la responsabilidad de calcular la puntuación. ¿Por qué era importante separar estas dos responsabilidades en clases separadas? Debido a que cada responsabilidad es un eje de cambio. Cuando los requerimientos cambien, ese cambio se manifestará a través de un cambio de responsabilidad entre las clases. Si una clase supone más de una responsabilidad, entonces habrá más de una razón para que cambie. Si una clase tiene más de una responsabilidad, entonces las responsabilidades se acoplan.
Los cambios en una responsabilidad pueden afectar o inhibir la capacidad de la clase para cumplir con las demás responsabilidades. Este tipo de acoplamiento conduce a diseños frágiles que se rompen de forma inesperada cuando se cambia.
Por ejemplo, considere el diseño en la Figura 9-1. La clase Rectangle tiene dos métodos que se muestran. Uno dibuja el rectángulo en la pantalla, el otro calcula el área del rectángulo. Dos aplicaciones diferentes utilizan la clase Rectangle. Una aplicación hace geometría computacional. Utiliza la clase Rectangle para realizar operaciones matemáticas sobre las figuras geométricas. Nunca dibuja el rectángulo en la pantalla. La otra aplicación es de carácter gráfico. También puede hacer un poco de geometría computacional, pero sin duda dibuja el rectángulo en la pantalla. Este diseño viola la SRP. La clase Rectangle tiene dos responsabilidades. La primera responsabilidad es proporcionar un modelo matemático de la geometría de un rectángulo. La segunda responsabilidad es hacer que el rectángulo se renderice en una interfaz gráfica de . La violación de SRP causa varios problemas desagradables. En primer lugar, debe incluir la interfaz gráfica de en la aplicación de geometría computacional. Si se tratara de una aplicación C + +, la interfaz gráfica de tendría que estar enlazada, consumiendo tiempo de enlace, tiempo de compilación, y footprint de memoria. En una aplicación Java, los archivos .class para la interfaz gráfica de tendrían que ser desplegados en la plataforma de destino. En segundo lugar, si un cambio en el GraphicalApplication hace que el rectángulo cambie por alguna razón, ese cambio puede obligar a reconstruir, volver a probar, y volver a implementar el ComputationalGeometryApplication. Si nos olvidamos de hacer esto, la aplicación se puede romper de forma impredecible. Un mejor diseño es separar las dos responsabilidades en dos clases completamente diferentes, como se muestra en la Figura 9.2. Este diseño mueve las partes cómputacionales de la clase
Rectangle en la clase GeometricRectangle. Ahora los cambios realizados en la forma
como
los
rectángulos
son
renderizados
no
pueden
afectar
a
la
ComputationalGeometryApplication.
¿Qué es una responsabilidad? En el contexto del principio de responsabilidad individual (SRP) se define la responsabilidad como "una razón para el cambio." Si usted puede pensar en más de un motivo para el cambio de una clase, entonces esa clase tiene más de una responsabilidad. Esto es a veces difícil de ver. Estamos acostumbrados a pensar en responsabilidad por grupos. Por ejemplo, considere la interfaz de módem en el Listing 9-1. La mayoría de nosotros estará de acuerdo en que esta interfaz parece perfectamente razonable. Las cuatro funciones que declara son sin duda las funciones pertenecientes a un módem.
Sin embargo, aquí se muestran dos responsabilidades. La primera responsabilidad es la gestión de la conexión. La segunda es la comunicación de datos. Las funciones dial y hangup gestionan la conexión del módem, mientras que las funciones send y recv comunican los datos. En este caso deberían separarse estas dos responsabilidades? Es casi seguro que sí deberían. Los dos conjuntos de funciones no tienen casi nada en común. Sin duda van a cambiar por diferentes razones. Por otra parte, se llamarán desde módulos completamente diferentes dentro de las aplicaciones que los utilizan. Esos módulos pueden cambiar por razones diferentes también. Por lo tanto el diseño en la Figura 9-3 es, probablemente, mejor. Separa las dos funciones en dos interfaces separadas. Esto, al menos, reduce el acoplamiento de las dos responsabilidades en las aplicaciones cliente.
Sin embargo, observe que se han reacoplado las dos responsabilidades en una clase única ModemImplementation. Esto no es deseable, pero puede que sea necesario. A menudo hay razones, que tienen que ver con los detalles del hardware o sistema operativo, que nos
obligan a acoplar cosas que preferiríamos no acoplar. Sin embargo, mediante la separación de sus interfaces, se han desacoplado los conceptos en cuanto al resto de la aplicación se refiere. Podemos ver la clase ModemImplementation es una chapuza, o una verruga en el diseño, sin embargo, observe que todas las relaciones de dependencia fluyen lejos de ésta. Nadie necesita depender de esta clase. Nadie, excepto main, necesita saber que existe (#ForeverAlone). Así, hemos puesto el bit feo detrás de una valla. Esa verruga no tiene por qué filtrarse y contaminar el resto de la aplicación. Conclusión El SRP es uno de los principios más sencillos, y uno de los más difíciles de acertar. Conjugar las responsabilidades es algo que hacemos naturalmente. Encontrar y separar las responsabilidades de unos a otros es de lo que el diseño de software trata en realidad. Adaptación del artículo: SRP: The Single Responsability Principle Martin, Robert C. Object Mentor, 1999