Aclarar el principio de responsabilidad única

62

El Principio de Responsabilidad Única establece que una clase debe hacer una y solo una cosa. Algunos casos son bastante claros. Sin embargo, otros son difíciles porque lo que parece "una cosa" cuando se ve en un nivel dado de abstracción puede ser varias cosas cuando se ve en un nivel inferior. También temo que si el Principio de Responsabilidad Única se respeta en los niveles más bajos, el código de ravioli detallado y desacoplado excesivamente, donde se gastan más líneas creando pequeñas clases para todo e información de plomería alrededor de lo que realmente resuelve el problema en cuestión, puede resultar. p>

¿Cómo describirías lo que significa "una cosa"? ¿Cuáles son algunos signos concretos de que una clase realmente hace más que "una cosa"?

    
pregunta dsimcha 05.11.2010 - 14:34

11 respuestas

50

Realmente me gusta la forma en que Robert C. Martin (Uncle Bob) reitera el Principio de Responsabilidad Única (vinculado a PDF) :

  

Nunca debe haber más de una razón para que cambie una clase

Es sutilmente diferente de la definición tradicional de "debería hacer solo una cosa", y me gusta esto porque te obliga a cambiar la forma en que piensas acerca de tu clase. En lugar de pensar en "¿esto está haciendo una cosa?", Piensa en qué puede cambiar y cómo afectan esos cambios a tu clase. Entonces, por ejemplo, si la base de datos cambia, ¿su clase necesita cambiar? ¿Qué sucede si el dispositivo de salida cambia (por ejemplo, una pantalla, un dispositivo móvil o una impresora)? Si su clase necesita cambiar debido a cambios de muchas otras direcciones, entonces eso es una señal de que su clase tiene demasiadas responsabilidades.

En el artículo vinculado, el tío Bob concluye:

  

El SRP es uno de los principios más simples y uno de los más difíciles de corregir. Unir responsabilidades es algo que hacemos naturalmente. Encontrar y separar aquellos   las responsabilidades mutuas son en gran medida de lo que realmente se trata el diseño de software.

    
respondido por el Paddyslacker 05.11.2010 - 18:27
17

Me sigo preguntando qué problema está intentando resolver SRP? ¿Cuándo me ayuda SRP? Esto es lo que se me ocurrió:

Debes refactorizar la responsabilidad / funcionalidad de una clase cuando:

1) Has duplicado la funcionalidad (DRY)

2) Encuentras que tu código necesita otro nivel de abstracción para ayudarte a entenderlo (KISS)

3) Los expertos en dominios entienden que las partes de la funcionalidad están separadas de un componente diferente (lenguaje ubicuo)

NO DEBE refactorizar la responsabilidad de una clase cuando:

1) No hay ninguna funcionalidad duplicada.

2) La funcionalidad no tiene sentido fuera del contexto de su clase. Dicho de otra manera, su clase proporciona un contexto en el cual es más fácil entender la funcionalidad.

3) Sus expertos en dominios no tienen un concepto de esa responsabilidad.

Se me ocurre que si el SRP se aplica a una gran parte del comercio, intercambiamos un tipo de complejidad (tratando de hacer cabezas o colas de una clase con demasiada actividad interna) con otro tipo (tratando de mantener a todos los colaboradores / niveles de abstracción directos con el fin de averiguar lo que realmente hacen estas clases).

En caso de duda, ¡déjalo fuera! Siempre puede refactorizar más tarde, cuando haya un caso claro para ello.

¿Qué piensas?

    
respondido por el Brandon 29.12.2010 - 20:26
4

No sé si hay una escala objetiva, pero lo que revelaría serían los métodos, no tanto la cantidad de ellos sino la variedad de su función. Estoy de acuerdo en que puedes llevar la descomposición demasiado lejos, pero no seguiría ninguna regla estricta al respecto.

    
respondido por el Jeremy 05.11.2010 - 14:39
4

La respuesta se encuentra en la definición

Lo que define la responsabilidad de ser, en última instancia, le da el límite.

Ejemplo :

Tengo un componente que tiene la responsabilidad de mostrar las facturas - > en este caso, si empecé a agregar algo más, estoy rompiendo el principio.

Por otro lado, si dije responsabilidad de gestionar las facturas - > agregar varias funciones más pequeñas (por ejemplo, imprimir Factura , actualizar Factura ) todas están dentro de ese límite.

Sin embargo, si ese módulo comenzó a manejar cualquier funcionalidad fuera de facturas , entonces estaría fuera de ese límite.

    
respondido por el Darknight 30.12.2010 - 02:40
2

Siempre lo veo en dos niveles:

  • Me aseguro de que mis métodos solo hagan una cosa y la hagan bien
  • Veo una clase como una agrupación lógica (OO) de aquellos métodos que representan una cosa bien

Entonces, algo como un objeto de dominio llamado Perro:

Dog es mi clase, ¡pero los perros pueden hacer muchas cosas! Es posible que tenga métodos como walk() , run() y bite(DotNetDeveloper spawnOfBill) (lo siento, no pude resistir; p).

Si Dog se vuelve demasiado difícil de manejar, entonces pensaría en cómo se pueden modelar grupos de esos métodos en otra clase, como una clase Movement , que podría contener mis métodos walk() y run() .

No hay reglas duras y rápidas, su diseño OO evolucionará con el tiempo. Intento buscar una interfaz clara / API pública, así como métodos simples que hagan bien una cosa y la otra.

    
respondido por el Martijn Verburg 05.11.2010 - 15:07
1

Lo miro más a lo largo de las líneas de una clase solo debería representar una cosa. Para el ejemplo apropiado de @ Karianna, tengo mi clase Dog , que tiene métodos para walk() , run() y bark() . No voy a agregar métodos para meaow() , squeak() , slither() o fly() porque no son cosas que hacen los perros. Son cosas que hacen otros animales, y esos otros animales tendrían sus propias clases para representarlos.

(Por cierto, si tu perro vuela , entonces probablemente deberías dejar de tirarlo por la ventana).

    
respondido por el JohnL 05.11.2010 - 18:57
1

Una clase debe hacer una cosa cuando se ve en su propio nivel de abstracción. Sin duda hará muchas cosas a un nivel menos abstracto. Así es como funcionan las clases para hacer que los programas sean más fáciles de mantener: ocultan los detalles de la implementación si no necesita examinarlos de cerca.

Yo uso los nombres de clase como una prueba para esto. Si no puedo dar un nombre descriptivo bastante corto a una clase, o si ese nombre tiene una palabra como "Y", probablemente esté violando el Principio de Responsabilidad Única.

En mi experiencia, es más fácil mantener este principio en los niveles más bajos, donde las cosas son más concretas.

    
respondido por el David Thornley 29.12.2010 - 21:15
0

Se trata de tener un único rol .

Cada clase debe ser reanudada por un nombre de rol. Un rol es de hecho un (conjunto de) verbo (s) asociado (s) a un contexto.

Por ejemplo:

El archivo proporciona acceso a un archivo. FileManager gestiona objetos de archivo.

Datos de retención de recursos para un recurso de un archivo. ResourceManager retiene y proporciona todos los recursos.

Aquí puede ver que algunos verbos como "administrar" implican un conjunto de otros verbos. Los verbos solos son mejor pensados como funciones que las clases, la mayoría del tiempo. Si el verbo implica demasiadas acciones que tienen su propio contexto común, entonces debería ser una clase en sí misma.

Por lo tanto, la idea es solo permitirle tener una idea simple de lo que hace la clase al definir un rol único, que podría ser el agregado de varios sub-roles (realizados por objetos miembros u otros objetos).

A menudo construyo clases de Manager que tienen varias clases diferentes. Como una fábrica, un registro, etc. Vea una clase de Gerente como una especie de jefe de grupo, un jefe de orquesta que guía a otras personas a trabajar juntos para lograr una idea de alto nivel. Tiene un rol, pero implica trabajar con otros roles únicos dentro. También puede verlo como la organización de una empresa: un CEO no es productivo en el nivel de productividad pura, pero si él no está allí, entonces nada puede funcionar correctamente juntos. Ese es su papel.

Cuando diseñes, identifica roles únicos. Y para cada función, nuevamente vea si no se puede cortar en otras funciones. De esa manera, si necesita cambiar de manera simple la forma en que su Gerente construye los objetos, simplemente cambie la Fábrica e ir pensando en la paz.

    
respondido por el Klaim 05.11.2010 - 16:24
-1

El SRP no solo consiste en dividir las clases, sino también en delegar funciones.

En el ejemplo de Dog utilizado anteriormente, no use SRP como justificación para tener 3 clases separadas como DogBarker, DogWalker, etc. (baja cohesión). En su lugar, observe la implementación de los métodos de una clase y decida si "saben demasiado". Aún puedes tener dog.walk (), pero probablemente el método walk () debería delegar en otra clase los detalles de cómo se logra caminar.

En efecto, estamos permitiendo que la clase de perros tenga una razón para cambiar: porque los perros cambian. Por supuesto, combinando esto con otros principios SÓLIDOS, extendería Dog para una nueva funcionalidad en lugar de cambiar Dog (abierto / cerrado). E inyectarías tus dependencias como IMove e IEat. Y, por supuesto, harías estas interfaces separadas (segregación de interfaces). Dog solo cambiaría si encontramos un error o si Dogs cambia fundamentalmente (Liskov Sub, no extienda y luego elimine el comportamiento).

El efecto neto de SOLID es que podemos escribir código nuevo con más frecuencia que para modificar el código existente, y eso es una gran ganancia.

    
respondido por el user1488779 18.07.2013 - 00:03
-1

Todo depende de la definición de responsabilidad y de cómo esa definición afectará el mantenimiento de su código. Todo se reduce a una cosa y así es como su diseño lo ayudará a mantener su código.

Y como alguien dijo "Caminar sobre el agua y diseñar software a partir de las especificaciones dadas es fácil, siempre que ambos estén congelados".

Entonces, si estamos definiendo la responsabilidad de una manera más concreta, entonces no necesitamos cambiarla.

A veces, las responsabilidades serán claramente obvias, pero a veces pueden ser sutiles y debemos decidir con criterio.

Supongamos que agregamos otra responsabilidad a la clase Dog llamada catchThief (). Ahora podría estar conduciendo hacia una responsabilidad adicional diferente. Mañana, si el Departamento de Policía tiene que cambiar la forma en que el perro está atrapando al ladrón, entonces habrá que cambiar la clase de perro. Sería mejor crear otra subclase en este caso y llamarlo ThiefCathcerDog. Pero desde una perspectiva diferente, si estamos seguros de que no cambiará en ninguna circunstancia, o si la forma en que se ha implementado catchThief depende de algún parámetro externo, entonces es perfectamente correcto tener esta responsabilidad. Si las responsabilidades no son sorprendentemente extrañas, entonces tenemos que decidirlas juiciosamente en función del caso de uso.

    
respondido por el AKS 02.08.2013 - 03:34
-1

"Una razón para cambiar" depende de quién está usando el sistema. Asegúrese de tener casos de uso para cada actor, y haga una lista de los cambios más probables, para cada posible cambio de caso de uso asegúrese de que solo una clase se vea afectada por ese cambio. Si está agregando un caso de uso completamente nuevo, asegúrese de que solo necesita extender una clase para hacerlo.

    
respondido por el kiwicomb123 03.07.2016 - 05:02

Lea otras preguntas en las etiquetas