El principio de responsabilidad única se define en wikipedia como
El principio de responsabilidad única es un principio de programación de computadoras que establece que cada módulo, clase o función debe tener responsabilidad sobre una sola parte de la funcionalidad proporcionada por el software, y esa responsabilidad debe estar completamente encapsulada por la clase
Si una clase solo debe tener una única responsabilidad, ¿cómo puede tener más de 1 método? ¿No tendría cada método una responsabilidad diferente, lo que significaría que la clase tendría más de 1 responsabilidad?
Todos los ejemplos que he visto que demuestran el principio de responsabilidad única utilizan una clase de ejemplo que solo tiene un método. Puede ser útil ver un ejemplo o tener una explicación de una clase con múltiples métodos que aún pueden considerarse como una responsabilidad.
fuente
Respuestas:
La responsabilidad única podría no ser algo que una sola función pueda cumplir.
Esta clase puede romper el principio de responsabilidad única. No porque tenga dos funciones, sino por el código
getX()
ygetY()
para satisfacer a diferentes partes interesadas que pueden exigir un cambio. Si el Vicepresidente Sr. X envía un memorando de que todos los números se expresarán como números de coma flotante y la Directora de Contabilidad, la Sra. Y insiste en que todos los números que revisa su departamento permanecerán enteros independientemente de lo que el Sr. X piense bien, entonces esta clase debería tener Una sola idea de quién es responsable porque las cosas están a punto de volverse confusas.Si se hubiera seguido SRP, estaría claro si la clase de ubicación contribuye a las cosas a las que están expuestos el Sr. X y su grupo. Deje en claro de qué es responsable la clase y sepa qué directiva impacta esta clase. Si ambos impactan en esta clase, entonces estaba mal diseñada para minimizar el impacto del cambio. "Una clase solo debe tener una razón para cambiar" no significa que toda la clase solo pueda hacer una pequeña cosa. Significa que no debería poder mirar la clase y decir que tanto el Sr. X como la Sra. Y tienen interés en esta clase.
Aparte de cosas como esas. No, múltiples métodos están bien. Simplemente dele un nombre que aclare qué métodos pertenecen a la clase y cuáles no.
El SRP del tío Bob tiene más que ver con la ley de Conway que con la ley de Curly . El tío Bob aboga por aplicar la Ley de Curly (hacer una cosa) a funciones, no a clases. SRP advierte contra los motivos de mezcla para cambiar juntos. La Ley de Conway dice que el sistema seguirá cómo fluye la información de una organización. Eso lleva a seguir SRP porque no te importa lo que nunca escuchas.
La gente sigue queriendo que SRP sea sobre todas las razones para limitar el alcance. Hay más razones para limitar el alcance que SRP. Limito aún más el alcance al insistir en que la clase sea una abstracción que puede tomar un nombre que garantice que mirar dentro no te sorprenda .
Puedes aplicar la Ley de Curly a las clases. Estás fuera de lo que habla el tío Bob, pero puedes hacerlo. Cuando te equivocas es cuando comienzas a pensar que eso significa una función. Es como pensar que una familia solo debe tener un hijo. Tener más de un hijo no impide que sea una familia.
Si aplica la ley de Curly a una clase, todo en la clase debe tratarse de una sola idea unificadora. Esa idea puede ser amplia. La idea podría ser la persistencia. Si algunas funciones de utilidad de registro están allí, entonces están claramente fuera de lugar. No importa si el Sr. X es el único a quien le importa este código.
El principio clásico para aplicar aquí se llama Separación de preocupaciones . Si separa todas sus preocupaciones, se podría argumentar que lo que queda en cualquier lugar es una preocupación. Así llamamos a esta idea antes de que la película de 1991 City Slickers nos presentara al personaje Curly.
Esto esta bien. Es solo que lo que el tío Bob llama responsabilidad no es una preocupación. Una responsabilidad hacia él no es algo en lo que te concentres. Es algo que puede obligarte a cambiar. Puede concentrarse en una preocupación y aún así crear código que sea responsable ante diferentes grupos de personas con diferentes agendas.
Tal vez no te importe eso. Multa. Pensar que sostener "hacer una cosa" resolverá todos sus problemas de diseño muestra una falta de imaginación de lo que "una cosa" puede llegar a ser. Otra razón para limitar el alcance es la organización. Puede anidar muchas "una cosa" dentro de otras "una cosa" hasta que tenga un cajón de basura lleno de todo. He hablado de eso antes
Por supuesto, la razón clásica de OOP para limitar el alcance es que la clase tiene campos privados y, en lugar de usar getters para compartir esos datos, colocamos todos los métodos que necesitan esos datos en la clase donde pueden usar los datos en privado. Muchos consideran que esto es demasiado restrictivo para usarlo como limitador de alcance porque no todos los métodos que pertenecen juntos usan exactamente los mismos campos. Me gusta asegurarme de que cualquier idea que reunió los datos sea la misma idea que unió los métodos.
La forma funcional de ver esto es eso
a.f(x)
ya.g(x)
son simplemente f a (x) y g a (x). No dos funciones, sino un continuo de pares de funciones que varían juntas. Ela
ni siquiera tiene que tener los datos en ella. Podría ser simplemente cómo sabes qué implementaciónf
yg
qué vas a usar. Las funciones que cambian juntas pertenecen juntas. Eso es buen viejo polimorfismo.SRP es solo una de las muchas razones para limitar el alcance. Es una buena. Pero no el único.
fuente
La clave aquí es el alcance o, si lo prefiere, la granularidad . Una parte de la funcionalidad representada por una clase se puede separar en partes de la funcionalidad, cada parte es un método.
Aquí hay un ejemplo. Imagine que necesita crear un CSV a partir de una secuencia. Si desea cumplir con RFC 4180, tomaría bastante tiempo implementar el algoritmo y manejar todos los casos límite.
Hacerlo en un solo método daría como resultado un código que no será particularmente legible, y especialmente, el método haría varias cosas a la vez. Por lo tanto, lo dividirá en varios métodos; por ejemplo, uno de ellos puede estar a cargo de generar el encabezado, es decir, la primera línea del CSV, mientras que otro método convertiría un valor de cualquier tipo a su representación de cadena adecuada para el formato CSV, mientras que otro determinaría si un el valor debe estar entre comillas dobles.
Esos métodos tienen su propia responsabilidad. El método que verifica si es necesario agregar comillas dobles o no tiene el suyo, y el método que genera el encabezado tiene uno. Este es SRP aplicado a los métodos .
Ahora, todos esos métodos tienen un objetivo en común, es decir, tomar una secuencia y generar el CSV. Esta es la responsabilidad única de la clase .
Pablo H comentó:
En efecto. El ejemplo CSV que di tiene idealmente un método público y todos los demás métodos son privados. Un mejor ejemplo sería una cola, implementada por una
Queue
clase. Esta clase contendría, básicamente, dos métodos:push
(también llamadoenqueue
) ypop
(también llamadodequeue
).La responsabilidad de
Queue.push
es agregar un objeto a la cola de la cola.La responsabilidad de
Queue.pop
es eliminar un objeto de la cabeza de la cola y manejar el caso donde la cola está vacía.La responsabilidad de la
Queue
clase es proporcionar una lógica de cola.fuente
Una función es una función.
Una responsabilidad es una responsabilidad.
Un mecánico tiene la responsabilidad de reparar automóviles, lo que implicará diagnósticos, algunas tareas de mantenimiento simples, algunos trabajos de reparación reales, algunas delegaciones de tareas a otros, etc.
Una clase de contenedor (lista, matriz, diccionario, mapa, etc.) tiene la responsabilidad de almacenar objetos, lo que implica almacenarlos, permitir su inserción, proporcionar acceso, algún tipo de orden, etc.
Una sola responsabilidad no significa que haya muy poco código / funcionalidad, significa que cualquier funcionalidad que haya "pertenece" bajo la misma responsabilidad.
fuente
La responsabilidad individual no necesariamente significa que solo hace una cosa.
Tomemos, por ejemplo, una clase de servicio de usuario:
Esta clase tiene múltiples métodos, pero su responsabilidad es clara. Proporciona acceso a los registros de usuario en el almacén de datos. Sus únicas dependencias son el modelo de usuario y el almacén de datos. Está ligeramente acoplado y es altamente cohesivo, que realmente es lo que SRP está tratando de hacer que pienses.
SRP no debe confundirse con el "principio de segregación de interfaz" (ver SÓLIDO ). El principio de segregación de interfaz (ISP) dice que las interfaces más pequeñas y livianas son preferibles que las interfaces más grandes y más generalizadas. Go hace un uso intensivo de ISP en toda su biblioteca estándar:
SRP e ISP están ciertamente relacionados, pero uno no implica el otro. ISP está en el nivel de interfaz y SRP está en el nivel de clase. Si una clase implementa varias interfaces simples, es posible que ya no tenga una sola responsabilidad.
Gracias a Luaan por señalar la diferencia entre ISP y SRP.
fuente
UserService
yUser
para ser UpperCamelCase, pero los métodosCreate
,Exists
yUpdate
habría hecho lowerCamelCase.Hay un chef en un restaurante. Su única responsabilidad es cocinar. Sin embargo, puede cocinar filetes, papas, brócoli y cientos de otras cosas. ¿Contrataría a un chef por plato en su menú? ¿O un chef para cada componente de cada plato? O un chef que puede cumplir con su única responsabilidad: ¿cocinar?
Si le pide a ese chef que haga la nómina también, es cuando viola SRP.
fuente
Contraejemplo: almacenamiento de estado mutable.
Supongamos que tiene la clase más simple, cuyo único trabajo es almacenar un
int
.Si se limitara a solo 1 método, podría tener un
setState()
, o ungetState()
, a menos que rompa la encapsulación y hagai
público.Claramente, esta responsabilidad única requiere tener al menos 2 métodos en esta clase. QED
fuente
Estás malinterpretando el principio de responsabilidad única.
La responsabilidad individual no equivale a un solo método. Significan cosas diferentes. En el desarrollo de software hablamos de cohesión . Las funciones (métodos) que tienen una alta cohesión "pertenecen" juntas y pueden considerarse como una responsabilidad única.
Depende del desarrollador diseñar el sistema para que se cumpla el principio de responsabilidad única. Uno puede ver esto como una técnica de abstracción y, por lo tanto, a veces es una cuestión de opinión. La implementación del principio de responsabilidad única hace que el código sea más fácil de probar y de comprender su arquitectura y diseño.
fuente
A menudo es útil (en cualquier idioma, pero especialmente en los idiomas OO) mirar las cosas y organizarlas desde el punto de vista de los datos en lugar de las funciones.
Por lo tanto, considere que la responsabilidad de una clase es mantener la integridad y proporcionar ayuda para utilizar correctamente los datos que posee. Claramente, esto es más fácil de hacer si todo el código está en una clase, en lugar de distribuirse en varias clases. Agregar dos puntos se hace de manera más confiable, y el código se mantiene más fácilmente, con un
Point add(Point p)
método en laPoint
clase que tenerlo en otro lugar.Y en particular, la clase no debe exponer nada que pueda resultar en datos inconsistentes o incorrectos. Por ejemplo, si un
Point
debe estar dentro de un plano (0,0) a (127,127), el constructor y cualquier método que modifique o produzca uno nuevoPoint
tienen la responsabilidad de verificar los valores que se les dan y rechazar cualquier cambio que pueda violar esto requisito. (A menudo, algo como aPoint
sería inmutable, y garantizar que no haya formas de modificar unPoint
después de que se construya también sería una responsabilidad de la clase)Tenga en cuenta que las capas aquí son perfectamente aceptables. Puede tener una
Point
clase para tratar puntos individuales y unaPolygon
clase para tratar un conjunto dePoint
s; estos todavía tienen responsabilidades separadas porquePolygon
delega toda la responsabilidad de tratar cualquier cosa que tenga que ver únicamente con unPoint
(como garantizar que un punto tenga tanto un valorx
como uny
valor) para laPoint
clase.fuente