Al usar el Principio de Responsabilidad Única, ¿qué constituye una "responsabilidad"?

187

Parece bastante claro que "Principio de Responsabilidad Única" no significa "solo hace una cosa". Para eso son los métodos.

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}

Bob Martin dice que "las clases deberían tener una sola razón para cambiar". Pero eso es difícil de entender si eres un programador nuevo en SOLID.

Escribí una respuesta a otra pregunta , donde sugerí que las responsabilidades son como títulos de trabajo y bailé el tema mediante el uso de una metáfora de restaurante para ilustrar mi punto. Pero eso todavía no articula un conjunto de principios que alguien podría usar para definir las responsabilidades de sus clases.

Entonces, ¿cómo lo haces? ¿Cómo determina qué responsabilidades debe tener cada clase y cómo define una responsabilidad en el contexto de SRP?

    
pregunta Robert Harvey 27.03.2017 - 21:32

16 respuestas

109

Una forma de comprender esto es imaginar posibles cambios en los requisitos en futuros proyectos y preguntarse qué necesitará hacer para que sucedan.

Por ejemplo:

  

Nuevo requisito comercial: los usuarios ubicados en California obtienen un descuento especial.

     

Ejemplo de cambio "bueno": necesito modificar el código en una clase que calcula los descuentos.

     

Ejemplo de cambios incorrectos: necesito modificar el código en la clase de Usuario, y ese cambio tendrá un efecto en cascada en otras clases que usan la clase de Usuario, incluidas las que no tienen nada que ver con descuentos, por ejemplo. Matrícula, enumeración y gestión.

O:

  

Nuevo requisito no funcional: comenzaremos a usar Oracle en lugar de SQL Server

     

Ejemplo de buen cambio: solo necesita modificar una sola clase en la capa de acceso a datos que determina cómo conservar los datos en los DTO.

     

Cambio incorrecto: necesito modificar todas mis clases de capa empresarial porque contienen lógica específica de SQL Server.

La idea es minimizar la huella de futuros cambios potenciales, restringiendo las modificaciones de código a un área de código por área de cambio.

Como mínimo, sus clases deben separar las preocupaciones lógicas de las preocupaciones físicas. Se puede encontrar un gran conjunto de ejemplos en el espacio de nombres System.IO : allí podemos encontrar varios tipos de flujos físicos (por ejemplo, FileStream , MemoryStream o NetworkStream ) y varios lectores y escritores ( BinaryWriter , TextWriter ) que funcionan en un nivel lógico. Al separarlos de esta manera, evitamos la explosión combinatoria: en lugar de necesitar FileStreamTextWriter , FileStreamBinaryWriter , NetworkStreamTextWriter , NetworkStreamBinaryWriter , MemoryStreamTextWriter y MemoryStreamBinaryWriter , simplemente conectas el escritor y el flujo y tú puede tener lo que quieras Luego, más adelante, podemos agregar, digamos, un XmlWriter , sin necesidad de volver a implementarlo para la memoria, el archivo y la red por separado.

    
respondido por el John Wu 27.03.2017 - 21:51
73

Hablando en términos prácticos, las responsabilidades están limitadas por aquellas cosas que es probable que cambien. Por lo tanto, desafortunadamente, no hay una manera científica o formulada de llegar a lo que constituye una responsabilidad. Es una llamada de juicio.

Se trata de lo que, en tu experiencia , es probable que cambie.

Tendemos a aplicar el lenguaje del principio en una rabia hiperbólica, literal y celosa. Solemos dividir las clases porque podrían cambiar, o en líneas que simplemente nos ayudan a resolver problemas. (La última razón no es inherentemente mala). Pero, el SRP no existe por su propio bien; está en servicio para crear software mantenible.

De nuevo, si las divisiones no están controladas por los cambios probables , no están verdaderamente en servicio al SRP 1 si YAGNI es más aplicable. Ambos sirven al mismo objetivo final. Y ambos son asuntos de juicio, con suerte juicios experimentados .

Cuando el tío Bob escribe sobre esto, sugiere que pensemos en "responsabilidad" en términos de "quién está pidiendo el cambio". En otras palabras, no queremos que la Parte A pierda sus empleos porque la Parte B solicitó un cambio.

  

Cuando escribe un módulo de software, quiere asegurarse de que cuando   se solicitan cambios, esos cambios solo pueden originarse de una sola   persona, o más bien, un solo grupo de personas estrechamente acopladas   representando una única función de negocio estrechamente definida. Tú quieres   aísle sus módulos de las complejidades de la organización como un   todo, y diseñe sus sistemas de manera que cada módulo sea responsable   (responde a) las necesidades de esa única función empresarial. ( Tío Bob: el principio de responsabilidad única   )

Los desarrolladores buenos y experimentados tendrán una idea de qué cambios son probables. Y esa lista mental variará algo según la industria y la organización.

Lo que constituye una responsabilidad en su aplicación particular, en su organización particular, es, en última instancia, una cuestión de experimentado . Se trata de lo que es probable que cambie. Y, en cierto sentido, se trata de quién posee la lógica interna del módulo.

1. Para ser claros, eso no significa que sean malas divisiones. Podrían ser divisiones geniales que mejoran dramáticamente la legibilidad del código. Solo significa que no están controlados por el SRP.

    
respondido por el svidgen 27.03.2017 - 23:00
27

Sigo "las clases deben tener solo una razón para cambiar".

Para mí, esto significa pensar en esquemas descabellados que el propietario de mi producto podría crear ("¡Necesitamos soporte móvil!", "¡Necesitamos ir a la nube!", "¡Necesitamos apoyar a chino!") . Los buenos diseños limitarán el impacto de estos esquemas a áreas más pequeñas y los harán relativamente fáciles de lograr. Los malos diseños significan ir a un montón de código y hacer un montón de cambios riesgosos.

La experiencia es lo único que he encontrado para evaluar adecuadamente la probabilidad de esos esquemas locos, ya que hacer que uno sea fácil puede hacer que otros dos sean más difíciles, y evaluar la bondad de un diseño. Los programadores experimentados pueden imaginar lo que tendrían que hacer para cambiar el código, lo que hay para morderles el culo y los trucos que facilitan las cosas. Los programadores experimentados tienen una buena idea de lo jodidos que están cuando el propietario del producto pide cosas locas.

En la práctica, encuentro que las pruebas unitarias ayudan aquí. Si su código es inflexible, será difícil de probar. Si no puede inyectar simulaciones u otros datos de prueba, probablemente no podrá inyectar ese código SupportChinese .

Otra métrica aproximada es el paso del ascensor. Los lanzamientos de ascensor tradicionales son "si estuvieras en un ascensor con un inversor, ¿puedes venderle una idea?". Las empresas nuevas deben tener descripciones simples y cortas de lo que están haciendo, en qué se centran. Del mismo modo, las clases (y funciones) deben tener una descripción simple de lo que hacen . No "esta clase implementa algunos fubar de modo que pueda usarlo en estos escenarios específicos". Algo que puedes decirle a otro desarrollador: "Esta clase crea usuarios". Si no puedes comunicárselo a otros desarrolladores, estás yendo a obtener errores.

    
respondido por el Telastyn 27.03.2017 - 22:23
23

Nadie lo sabe. O al menos, no podemos ponernos de acuerdo en una definición. Eso es lo que hace que SPR (y otros principios SÓLIDOS) sea bastante controvertido.

Yo diría que ser capaz de descubrir qué es o no una responsabilidad es una de las habilidades que el desarrollador de software tiene que aprender a lo largo de su carrera. Cuanto más código escriba y revise, más experiencia tendrá para determinar si algo es responsabilidad única o múltiple. O si la responsabilidad individual está fracturada en partes separadas del código.

Yo diría que el propósito principal de SRP no es ser una regla difícil. Es para recordarnos que tengamos en cuenta la cohesión en el código y que siempre hagamos un esfuerzo consciente para determinar qué código es cohesivo y qué no.

    
respondido por el Euphoric 27.03.2017 - 22:24
5

Creo que el término "responsabilidad" es útil como metáfora porque nos permite usar el software para investigar qué tan bien está organizado el software. En particular, me enfocaré en dos principios:

  • La responsabilidad es proporcional a la autoridad.
  • No hay dos entidades que deban ser responsables de lo mismo.

Estos dos principios nos permiten repartir la responsabilidad de manera significativa porque se juegan unos contra otros. Si está habilitando un código para que haga algo por usted, debe ser responsable de lo que hace. Esto causa la responsabilidad de que una clase tenga que crecer, expandiendo su "una razón para cambiar" a ámbitos cada vez más amplios. Sin embargo, a medida que amplía las cosas, naturalmente comienza a encontrarse en situaciones en las que varias entidades son responsables de lo mismo. Esto está lleno de problemas con la responsabilidad de la vida real, por lo que seguramente también es un problema en la codificación. Como resultado, este principio hace que los ámbitos se reduzcan, al subdividir la responsabilidad en parcelas no duplicadas.

Además de estos dos, un tercer principio parece razonable:

  • La responsabilidad puede ser delegada

Considere un programa recién acuñado ... una pizarra en blanco. Al principio, solo tienes una entidad, que es el programa en su totalidad. Es responsable de ... todo. Naturalmente, en algún momento, comenzará a delegar responsabilidades en funciones o clases. En este punto, las dos primeras reglas entran en juego y te obligan a equilibrar esa responsabilidad. El programa de nivel superior sigue siendo responsable de la producción general, al igual que un gerente es responsable de la productividad de su equipo, pero a cada subentidad se le ha delegado la responsabilidad, y con ello la autoridad para llevar a cabo esa responsabilidad.

Como una ventaja adicional, esto hace que SOLID sea particularmente compatible con cualquier desarrollo de software corporativo que pueda ser necesario. Todas las compañías del planeta tienen algún concepto de cómo delegar responsabilidades y no todas están de acuerdo. Si delegas la responsabilidad en tu software de una manera que recuerda a la propia delegación de tu empresa, será mucho más fácil para los futuros desarrolladores llegar a la velocidad de cómo haces las cosas en esta empresa.

    
respondido por el Cort Ammon 28.03.2017 - 23:17
5

En esta conferencia en Yale, Uncle Bob da este ejemplo divertido :

DicequeEmployeetienetresrazonesparacambiar,tresfuentesderequisitosdecambio,yledaaestehumorísticoeirónico,peronoobstante,unaexplicaciónilustrativa:

  
  • SielmétodoCalcPay()tieneunerrorylecuestaalacompañíamillonesdedólares,elCFOlodespedirá.

  •   
  • SielmétodoReportHours()tieneunerrorylecuestaalacompañíamillonesdedólares,elCOOlodespedirá.

  •   
  • SielmétodoWriteEmmployee()tieneunerrorquecausaelborradodeunagrancantidaddedatosylecuestaalacompañíamillonesdedólares,el  ElCTOtedespedirá.

  •   

Porlotanto,tenertresejecutivosdenivelCdiferentespodríadespedirtepor  errorescostososenlamismaclasesignificaquelaclasetienedemasiados  responsabilidades.

DaestasoluciónqueresuelvelaviolacióndeSRP,peroaúnasítienequeresolverlaviolacióndeDIPquenosemuestraenelvideo.

    
respondido por el Tulains Córdova 29.03.2017 - 01:47
3

Creo que una mejor manera de subdividir las cosas que las "razones para cambiar" es comenzar por pensar si tendría sentido requerir que el código que debe realizar realice dos (o más) acciones deba mantener un referencia de objeto separada para cada acción, y si sería útil tener un objeto público que podría realizar una acción pero no la otra.

Si las respuestas a ambas preguntas son afirmativas, eso sugeriría que las acciones deben realizarse por clases separadas. Si las respuestas a ambas preguntas son no, eso sugeriría que, desde un punto de vista público, debería haber una clase; si el código para eso sería difícil de manejar, puede subdividirse internamente en clases privadas. Si la respuesta a la primera pregunta es no, pero la segunda es sí, debería haber una clase separada para cada acción más una clase compuesta que incluya referencias a instancias de las otras.

Si uno tiene clases separadas para el teclado, el beeper, la lectura numérica, la impresora de recibos y el cajón de efectivo de una caja registradora, y no hay una clase compuesta para una caja registradora completa, entonces el código que se supone que procesa una transacción puede terminar accidentalmente invocado de una manera que toma la entrada del teclado de una máquina, produce ruido a partir de la señal acústica de una segunda máquina, muestra los números en la pantalla de una tercera máquina, imprime un recibo en la impresora de una cuarta máquina y abre el cajón de efectivo de una quinta máquina. Cada una de esas funciones secundarias podría ser manejada por una clase separada, pero también debería haber una clase compuesta que se una a ellas. La clase compuesta debe delegar la mayor cantidad de lógica posible a las clases constituyentes, pero cuando sea práctico, debe ajustar las funciones de sus componentes constituyentes en lugar de requerir que el código del cliente acceda directamente a los constituyentes.

Se podría decir que la "responsabilidad" de cada clase es incorporar alguna lógica real o proporcionar un punto de conexión común para muchas otras clases que lo hacen, pero lo importante es centrarse ante todo en cómo debe ver el código del cliente. una clase. Si tiene sentido que el código del cliente vea algo como un solo objeto, entonces el código del cliente debería verlo como un solo objeto.

    
respondido por el supercat 28.03.2017 - 01:01
3

Es difícil conseguir SRP. Es principalmente una cuestión de asignar 'trabajos' a su código y asegurarse de que cada parte tenga responsabilidades claras. Como en la vida real, en algunos casos, dividir el trabajo entre personas puede ser bastante natural, pero en otros casos puede ser realmente complicado, especialmente si no los conoce (o el trabajo).

Siempre te recomiendo que simplemente escriba un código simple que funcione primero , luego refactorice un poco: tenderá a ver cómo el código se agrupa naturalmente después de un tiempo. Creo que es un error forzar responsabilidades antes de conocer el código (o las personas) y el trabajo a realizar.

Una cosa que notará es cuando el módulo comienza a hacer demasiado y es difícil de depurar / mantener. Este es el momento para refactorizar; ¿Cuál debería ser el trabajo principal y qué tareas se podrían asignar a otro módulo? Por ejemplo, ¿debería manejar las verificaciones de seguridad y el otro trabajo, o debería hacer las verificaciones de seguridad en otro lugar primero, o esto hará que el código sea más complejo?

Use demasiadas indirectas y se volverá un desastre otra vez ... en cuanto a otros principios, este estará en conflicto con otros, como KISS, YAGNI, etc. Todo es una cuestión de equilibrio.

    
respondido por el Christophe Roussy 28.03.2017 - 10:35
3

"Principio de responsabilidad única" es quizás un nombre confuso. "Sólo una razón para cambiar" es una mejor descripción del principio, pero aún así es fácil de malinterpretar. No estamos hablando de decir qué hace que los objetos cambien de estado en tiempo de ejecución. Estamos analizando qué podría hacer que los desarrolladores tengan que cambiar el código en el futuro.

A menos que estemos solucionando un error, el cambio se deberá a un requisito comercial nuevo o modificado. Tendrá que pensar fuera del código en sí, e imaginar qué factores externos podrían hacer que los requisitos cambien de forma independiente . Diga:

  • Los tipos impositivos cambian debido a una decisión política.
  • El marketing decide cambiar los nombres de todos los productos
  • La interfaz de usuario debe ser rediseñada para ser accesible
  • La base de datos está congestionada, por lo que necesita hacer algunas optimizaciones
  • Tienes que acomodar una aplicación móvil
  • y así sucesivamente ...

Lo ideal es que quieras que factores independientes afecten a diferentes clases. P.ej. ya que las tasas de impuestos cambian independientemente de los nombres de los productos, los cambios no deben afectar a las mismas clases. De lo contrario, corre el riesgo de que se produzca una introducción de cambio de impuestos y se produzca un error en la denominación del producto, que es el tipo de acoplamiento ajustado que desea evitar con un sistema modular.

Así que no solo se centre en lo que podría cambiar, cualquier cosa podría cambiar en el futuro. Concéntrese en lo que independientemente puede cambiar. Los cambios son típicamente independientes si son causados por diferentes actores.

Su ejemplo con títulos de trabajo está en el camino correcto, ¡pero debe tomarlo más literalmente! Si la mercadotecnia puede causar cambios en el código y las finanzas pueden causar otros cambios, estos cambios no deberían afectar al mismo código, ya que estos son literalmente títulos de trabajo diferentes y, por lo tanto, los cambios ocurrirán de manera independiente.

Para citar al tío Bob que inventó el término:

  

Cuando escribe un módulo de software, quiere asegurarse de que cuando   se solicitan cambios, esos cambios solo pueden originarse de una sola   persona, o más bien, un solo grupo de personas estrechamente acopladas   representando una única función de negocio estrechamente definida. Tú quieres   aísle sus módulos de las complejidades de la organización como un   todo, y diseñe sus sistemas de manera que cada módulo sea responsable   (responde a) las necesidades de esa única función empresarial.

Para resumir: una "responsabilidad" es atender a una única función empresarial. Si más de un actor puede hacer que tengas que cambiar una clase, entonces la clase probablemente rompe este principio.

    
respondido por el JacquesB 28.03.2017 - 17:04
2

Un buen artículo que explica los principios de programación SOLID y da ejemplos de código que siguen y no siguen estos principios es enlace .

En el ejemplo relacionado con SRP, da un ejemplo de algunas clases de formas (círculo y cuadrado) y una clase diseñada para calcular el área total de múltiples formas.

En su primer ejemplo, crea la clase de cálculo de área y hace que devuelva su salida como HTML. Más tarde, decide que quiere mostrarlo como JSON y tiene que cambiar su clase de cálculo de área.

El problema con este ejemplo es que su clase de cálculo de área es responsable de calcular el área de formas Y de mostrar esa área. Luego pasa por una forma mejor de hacerlo utilizando otra clase diseñada específicamente para mostrar áreas.

Este es un ejemplo simple (y más fácil de entender al leer el artículo, ya que tiene fragmentos de código), pero demuestra la idea central de SRP.

    
respondido por el blitz1616 27.03.2017 - 22:21
0

En primer lugar, lo que tienes son en realidad dos problemas separados : el problema de qué métodos poner en tus clases y el problema de la expansión de la interfaz.

Interfaces

Tienes esta interfaz:

public Interface CustomerCRUD
{
  public void Create(Customer customer);
  public Customer Read(int CustomerID);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Presumible, tiene varias clases que se ajustan a la interfaz CustomerCRUD (de lo contrario, una interfaz no es necesaria) y alguna función do_crud(customer: CustomerCRUD) que admite un objeto conforme. Pero ya ha roto el SRP: ha vinculado estas cuatro operaciones distintas.

Digamos que más adelante operaría en vistas de base de datos. Una vista de base de datos tiene solo el método Read disponible para ella. Pero desea escribir una función do_query_stuff(customer: ???) que los operadores de forma transparente en tablas o vistas en toda regla; después de todo, solo utiliza el método Read .

Crea una interfaz

Interfaz pública CustomerReader   {     Lectura del cliente público (customerID: int)   }

y factorice su interfaz CustomerCrud como:

public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Pero no hay un final a la vista. Podría haber objetos que podamos crear pero no actualizar, etc. Este agujero de conejo es demasiado profundo. La única forma sensata de cumplir con el principio de responsabilidad única es hacer que todas sus interfaces contengan exactamente un método . Go realmente sigue esta metodología de lo que he visto, con la gran mayoría de las interfaces que contienen una sola función; Si desea especificar una interfaz que contenga dos funciones, tiene que crear una nueva interfaz que combine ambas. Pronto obtendrás una explosión combinatoria de interfaces.

La salida de este lío es usar subtipos estructurales (implementados en, por ejemplo, OCaml) en lugar de interfaces (que son una forma de subtipo nominal). No definimos interfaces; en su lugar, simplemente podemos escribir una función

let do_customer_stuff customer = customer.read ... customer.update ...

que llama a los métodos que nos gustan OCaml utilizará la inferencia de tipos para determinar que podemos pasar cualquier objeto que implemente estos métodos. En este ejemplo, se determinaría que customer tiene el tipo <read: int -> unit, update: int -> unit, ...> .

Clases

Esto resuelve el interfaz desorden; pero aún tenemos que implementar clases que contengan múltiples métodos. Por ejemplo, ¿deberíamos crear dos clases diferentes, CustomerReader y CustomerWriter ? ¿Qué pasa si queremos cambiar la forma en que se leen las tablas (por ejemplo, ahora almacenamos en caché nuestras respuestas en redis antes de recuperar los datos), pero ahora cómo se escriben? Si sigues esta cadena de razonamiento hasta su conclusión lógica, eres conducido de manera inextricable a la programación funcional :)

    
respondido por el gardenhead 28.03.2017 - 00:14
0

En mi opinión, lo más parecido a un SRP que se me ocurre es un flujo de uso. Si no tiene un flujo de uso claro para una clase determinada, es probable que su clase tenga un olor a diseño.

Un flujo de uso sería una sucesión de llamadas de un método dado que le daría un resultado esperado (por lo tanto, verificable). Básicamente, define una clase con los casos de uso que obtuvo en mi humilde opinión, por eso toda la metodología del programa se centra en las interfaces sobre la implementación.

    
respondido por el Arthur Havlicek 28.03.2017 - 23:43
0

Es para lograr que múltiples requisitos cambien, no requiere que su componente cambie .

Pero buena suerte entendiendo eso a primera vista, cuando escuchas por primera vez sobre SOLID.

Veo muchos comentarios que dicen que SRP y YAGNI pueden contradecirse, pero YAGN lo hice cumplir por TDD (GOOS, London School) me enseñó a pensar y diseñar mis componentes desde la perspectiva de un cliente. Comencé a diseñar mis interfaces con lo que menos quisiera que hiciera un cliente, eso es lo poco que debería hacer . Y ese ejercicio se puede hacer sin ningún conocimiento de TDD.

Me gusta la técnica descrita por el tío Bob (no recuerdo de dónde, tristemente), que dice algo así como:

  

Pregúntese, ¿qué hace esta clase?

     

¿Tu respuesta contiene cualquiera de Y o Or

     

Si es así, extraiga esa parte de la respuesta, es responsabilidad suya

Esta técnica es absoluta, y como dijo @svidgen, SRP es una decisión de juicio, pero cuando se aprende algo nuevo, los absolutos son los mejores, es más fácil simplemente siempre hacer algo. Asegúrese de que la razón por la que no se separa es; una estimación informada, y no porque no sabes cómo hacerlo. Este es el arte, y se necesita experiencia.

Creo que muchas de las respuestas parecen hacer un argumento para desacoplarse cuando se habla de SRP .

SRP es no para asegurarse de que un cambio no se propague hacia abajo en el gráfico de dependencia.

Teóricamente, sin SRP , no tendrías ninguna dependencia ...

Un cambio no debería causar un cambio en muchos lugares de la aplicación, pero tenemos otros principios para eso. Sin embargo, SRP mejora el principio cerrado abierto . Este principio es más acerca de la abstracción, sin embargo, las abstracciones más pequeñas son más fáciles de volver a implementar .

Entonces, cuando enseñe SOLID como un todo, tenga cuidado de enseñar que SRP le permite cambiar menos código cuando cambian los requisitos, cuando de hecho, le permite escribir menos el código nuevo .

    
respondido por el Chris Wohlert 29.03.2017 - 16:29
0

No hay una respuesta clara a eso. Aunque la pregunta es estrecha, las explicaciones no lo son.

Para mí, es algo como Occam's Razor si quieres. Es un ideal donde intento medir mi código actual contra. Es difícil concretarlo en palabras simples y sencillas. Otra metáfora sería "un tema" que es tan abstracto, es decir, difícil de entender, como "responsabilidad única". Una tercera descripción sería "tratar con un nivel de abstracción".

¿Qué significa eso prácticamente?

Últimamente utilizo un estilo de codificación que consta principalmente de dos fases:

La fase I se describe mejor como un caos creativo. En esta fase escribo el código a medida que los pensamientos fluyen, es decir, crudos y feos.

La fase II es todo lo contrario. Es como limpiar después de un huracán. Esto requiere más trabajo y disciplina. Y luego miro el código desde la perspectiva de un diseñador.

Estoy trabajando principalmente en Python ahora, lo que me permite pensar en objetos y clases más adelante. Primero Fase I : escribo solo funciones y las distribuyo casi al azar en diferentes módulos. En Phase II , después de que empecé a hacer las cosas, tengo una visión más detallada de qué módulo trata con qué parte de la solución. Y mientras hojeo los módulos, temas son emergentes para mí. Algunas funciones están relacionadas temáticamente. Estos son buenos candidatos para clases . Y después de convertir las funciones en clases, lo que casi se hace con sangría y agregar self a la lista de parámetros en python;), uso SRP como Occam's Razor para eliminar la funcionalidad de otros módulos y clases.

Un ejemplo actual puede ser escribir una pequeña funcionalidad de exportación el otro día.

Hubo la necesidad de csv , excel y combinó hojas de excel en un zip.

La funcionalidad simple se realizó en tres vistas (= funciones). Cada función utiliza un método común para determinar los filtros y un segundo método para recuperar los datos. Luego, en cada función, se realizó la preparación de la exportación y se entregó como una Respuesta del servidor.

Hubo demasiados niveles de abstracción mezclados:

I) tratar con la solicitud / respuesta entrante / saliente

II) determinando filtros

III) recuperando datos

IV) transformación de datos

El paso fácil fue usar una abstracción ( exporter ) para tratar las capas II-IV en un primer paso.

Lo único que quedó fue el tema sobre solicitudes / respuestas . En el mismo nivel de abstracción, está extrayendo los parámetros de solicitud , lo que está bien. Así que tenía para esta vista una "responsabilidad".

En segundo lugar, tuve que dividir el exportador, que, como vimos, consistía en al menos otras tres capas de abstracción.

Determinar los criterios de filtro y retrival reales están casi en el mismo nivel de abstracción (los filtros son necesarios para obtener el subconjunto correcto de los datos). Estos niveles se colocaron en algo así como una capa de acceso a datos .

En el siguiente paso rompí los mecanismos de exportación reales: donde se necesitaba escribir en un archivo temporal, dividí eso en dos "responsabilidades": una para la escritura real de los datos en el disco y otra parte que se ocupaba de la formato actual

A lo largo de la formación de las clases y los módulos, las cosas se aclararon, lo que pertenecía a dónde. Y siempre la pregunta latente, si la clase hace demasiado .

  

¿Cómo determina qué responsabilidades debe tener cada clase y cómo define una responsabilidad en el contexto de SRP?

Es difícil dar una receta para seguir. Por supuesto, podría repetir la críptica "un nivel de abstracción": descartar si eso ayuda.

En mi mayor parte, para mí es un tipo de "intuición artística" que conduce al diseño actual; Yo modelo el código como un artista puede esculpir arcilla o hacer pintura.

Imagíname como un Coding Bob Ross ;)

    
respondido por el Thomas Junk 30.03.2017 - 21:50
0

Lo que trato de hacer para escribir el código que sigue al SRP:

  • Elija un problema específico que necesite resolver;
  • Escriba el código que lo resuelve, escriba todo en un método (p. ej .: main);
  • Analice cuidadosamente el código y, según el negocio, intente definir las responsabilidades que son visibles en todas las operaciones que se están realizando (esta es la parte subjetiva que también depende del negocio / proyecto / cliente);
  • Tenga en cuenta que toda la funcionalidad ya está implementada; lo que sigue es solo la organización del código (de ahora en adelante, no se implementarán funciones o mecanismos adicionales en este enfoque);
  • En función de las responsabilidades que definió en los pasos anteriores (que se definen en función de la idea de negocio y "una razón para cambiar"), extraiga una clase o método separado para cada uno;
  • Tenga en cuenta que este enfoque solo se preocupa por el SPR; idealmente, debería haber pasos adicionales aquí que intenten adherirse a los otros principios también.

Ejemplo :

Problema: obtenga dos números del usuario, calcule su suma y envíe el resultado al usuario:

//first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}

A continuación, intente definir responsabilidades en función de las tareas que deben realizarse. De esto, extraiga las clases apropiadas:

//Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}

Entonces, el programa refactorizado se convierte en:

//Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}

Nota: este ejemplo muy simple tiene en cuenta solo el principio de SRP. El uso de los otros principios (por ejemplo: el código "L" debería depender de abstracciones en lugar de concreciones) proporcionaría más beneficios al código y lo haría más fácil de mantener para los cambios de negocios.

    
respondido por el Emerson Cardoso 03.10.2017 - 21:07
0

Del libro de Robert C. Martins Arquitectura limpia: una guía artesanal para la estructura de software y Diseño , publicado el 10 de septiembre de 2017, Robert escribe lo siguiente:

  

Históricamente, el SRP se ha descrito de esta manera:

     

Un módulo debe tener una, y solo una, razón para cambiar

     

Los sistemas de software se cambian para satisfacer a los usuarios y partes interesadas; aquellos   los usuarios y las partes interesadas son la "razón para cambiar". que el   Principio del que se habla. De hecho, podemos reformular el principio de   di esto:

     

Un módulo debe ser responsable ante uno y solo uno, usuario o parte interesada

     

Desafortunadamente, la palabra "usuario" y "parte interesada" no son realmente el   palabra correcta para usar aquí. Probablemente habrá más de un usuario o   parte interesada que quiere que el sistema cambie de manera sana. En lugar   Realmente nos referimos a un grupo: una o más personas que requieren   ese cambio Nos referiremos a ese grupo como actor .

     

Por lo tanto, la versión final del SRP es:

     

Un módulo debe ser responsable ante un solo actor.

Así que esto no es sobre código. El SRP se trata de controlar el flujo de requisitos y las necesidades comerciales, que solo pueden provenir de una fuente.

    
respondido por el 4rchit3ct 23.07.2018 - 22:10

Lea otras preguntas en las etiquetas

Comentarios Recientes

<| endoftext |> Postmoderación, segunda enmienda La ley de Queensland protege el derecho a portar armas con interpretaciones tradicionales de la ley, la política y la comunidad. Pero los delincuentes pueden atacar nuestra pureza cultural; la legislación local debe tener en cuenta esos valores; la comunidad y los oficiales deben prepararse para responder. <| endoftext |> NOAA está trabajando activamente para abordar el cambio climático y contribuir a su sostenibilidad a largo plazo. Esto incluye nuevos conceptos... Lee mas