MVC: ¿El controlador rompe el principio de responsabilidad única?

16

El Principio de Responsabilidad Única establece que "una clase debería tener una razón para el cambio".

En el patrón MVC, el trabajo del controlador es mediar entre la vista y el modelo. Ofrece una interfaz para que la Vista informe acciones realizadas por el usuario en la GUI (por ejemplo, permitiendo que la Vista llame controller.specificButtonPressed()), y puede llamar a los métodos apropiados en el Modelo para manipular sus datos o invocar sus operaciones (por ejemplo model.doSomething()) .

Esto significa que:

  • El Controlador necesita saber acerca de la GUI para poder ofrecer a la Vista una interfaz adecuada para informar sobre las acciones del usuario.
  • También necesita saber acerca de la lógica en el Modelo, para poder invocar los métodos apropiados en el Modelo.

Eso significa que tiene dos razones para cambiar : un cambio en la GUI y un cambio en la lógica de negocios.

Si la GUI cambia, por ejemplo, se agrega un nuevo botón, el Controlador podría necesitar agregar un nuevo método para permitir que la Vista informe que un usuario presiona este botón.

Y si la lógica de negocios en el Modelo cambia, el Controlador podría tener que cambiar para invocar los métodos correctos en el Modelo.

Por lo tanto, el controlador tiene dos posibles razones para cambiar . ¿Rompe SRP?

Aviv Cohn
fuente
2
Un controlador es una calle de 2 vías, no es su enfoque típico de arriba hacia abajo o de abajo hacia arriba. No hay posibilidad de que abstraiga una de sus dependencias porque el controlador es la abstracción misma. Debido a la naturaleza del patrón, no es posible adherirse al SRP aquí. En resumen: sí, viola SRP pero eso es inevitable.
Jeroen Vannevel
1
¿Cuál es el punto de la pregunta? Si todos respondemos "sí, lo hace", ¿entonces qué? ¿Qué pasa si la respuesta es "no"? ¿Cuál es el verdadero problema que estás tratando de resolver con esta pregunta?
Bryan Oakley
1
"una razón para cambiar" no significa "código que cambia". Si hace siete errores tipográficos en sus nombres de variables para la clase, ¿esa clase ahora tiene 7 responsabilidades? No. Si tiene más de una variable o más de una función, también podría tener una sola responsabilidad.
Bob

Respuestas:

14

Si continúa razonando sobre el SRP, notará que "responsabilidad única" es en realidad un término esponjoso. Nuestro cerebro humano es de alguna manera capaz de distinguir entre diferentes responsabilidades y múltiples responsabilidades pueden resumirse en una responsabilidad "general". Por ejemplo, imagine que en una familia común de 4 personas hay un miembro de la familia responsable de preparar el desayuno. Ahora, para hacer esto, uno tiene que hervir huevos y tostar pan y, por supuesto, preparar una taza saludable de té verde (sí, el té verde es lo mejor). De esta forma, puede dividir "preparar el desayuno" en trozos más pequeños que se resumen juntos en "preparar el desayuno". Tenga en cuenta que cada pieza también es una responsabilidad que, por ejemplo, podría delegarse a otra persona.

Volviendo al MVC: si la mediación entre el modelo y la vista no es una responsabilidad sino dos, entonces, ¿cuál sería la siguiente capa de abstracción anterior, combinando esas dos? Si no puede encontrar uno, no lo resumió correctamente o no hay ninguno, lo que significa que lo hizo bien. Y creo que ese es el caso con un controlador, que maneja una vista y un modelo.

valenterry
fuente
1
Solo como una nota adicional. Si tiene controller.makeBreakfast () y controller.wakeUpFamily (), ese controlador estaría rompiendo SRP, pero no porque sea un controlador, solo porque tiene más de una responsabilidad.
Bob
Gracias por responder, no estoy seguro de seguirte. ¿Acepta que el controlador tiene más de una responsabilidad? Creo que la razón es porque tiene dos razones para cambiar (creo): un cambio en el modelo y un cambio en la vista. ¿Estás de acuerdo con esto?
Aviv Cohn
1
Sí, puedo aceptar que el controlador tiene más de una responsabilidad. Sin embargo, estas son responsabilidades "menores" y eso no es un problema porque en el nivel de abstracción más alto solo tiene una responsabilidad (combinar las más bajas que usted menciona) y, por lo tanto, no viola el SRP.
valenterry
1
Encontrar el nivel correcto de abstracción es definitivamente importante. El ejemplo de "preparar el desayuno" es bueno: para completar una sola responsabilidad, a menudo hay una serie de tareas que deben cumplirse. Mientras el controlador solo orquesta estas tareas, sigue a SRP. Pero si sabe demasiado sobre hervir huevos, hacer tostadas o preparar té, violaría el SRP.
Allan
Esta respuesta tiene sentido para mí. Gracias Valenterry
J86
9

Si una clase tiene "dos posibles razones para cambiar", entonces sí, viola SRP.

Un controlador generalmente debe ser liviano y tener la responsabilidad única de manipular el dominio / modelo en respuesta a algún evento guiado por GUI. Podemos considerar que cada una de estas manipulaciones son básicamente casos de uso o características.

Si se agrega un nuevo botón en la GUI, el controlador solo debería cambiar si ese nuevo botón representa alguna función nueva (es decir, opuesta al mismo botón que existía en la pantalla 1 pero que aún no existía en la pantalla 2, y es entonces agregado a la pantalla 2). Tendría que haber un nuevo cambio correspondiente en el modelo también, para admitir esta nueva funcionalidad / característica. El controlador todavía tiene la responsabilidad de manipular el dominio / modelo en respuesta a algún evento impulsado por GUI.

Si la lógica de negocios en el modelo cambia debido a que se solucionó un error y requiere que el controlador cambie, entonces este es un caso especial (o tal vez el modelo está violando el principal abierto-cerrado). Si la lógica de negocios en el modelo cambia para admitir alguna funcionalidad / característica nueva, entonces eso no necesariamente afecta al controlador, solo si el controlador necesita exponer esa característica (que casi siempre sería el caso, de lo contrario, ¿por qué se agregaría a el modelo de dominio si no se usará). Entonces, en este caso, el controlador también debe modificarse, para admitir la manipulación del modelo de dominio de esta nueva forma en respuesta a algún evento impulsado por GUI.

Si el controlador tiene que cambiar porque, por ejemplo, la capa de persistencia se cambia de un archivo plano a una base de datos, entonces el controlador ciertamente está violando SRP. Si el controlador siempre funciona en la misma capa de abstracción, eso puede ayudar a lograr SRP.

Jordán
fuente
4

El controlador no viola SRP. Como usted dice, su responsabilidad es mediar entre los modelos y la vista.

Dicho esto, el problema con su ejemplo es que está vinculando los métodos de controlador a la lógica en la vista, es decir controller.specificButtonPressed. Nombrar los métodos de esta manera vincula el controlador a su GUI, no ha podido abstraer correctamente las cosas. El controlador debe tratar de realizar acciones específicas, es decir, controller.saveDatao controller.retrieveEntry. Agregar un nuevo botón en la GUI no significa necesariamente agregar un nuevo método al controlador.

Al presionar un botón en la vista, significa hacer algo, pero lo que sea que se haya hecho podría haberse activado fácilmente de cualquier otra manera o incluso a través de la vista.

Del artículo de Wikipedia sobre SRP

Martin define una responsabilidad como una razón para cambiar, y concluye que una clase o módulo debe tener una, y solo una, razón para cambiar. Como ejemplo, considere un módulo que compila e imprime un informe. Tal módulo se puede cambiar por dos razones. Primero, el contenido del informe puede cambiar. En segundo lugar, el formato del informe puede cambiar. Estas dos cosas cambian por causas muy diferentes; uno sustantivo y otro cosmético. El principio de responsabilidad única dice que estos dos aspectos del problema son realmente dos responsabilidades separadas y, por lo tanto, deben estar en clases o módulos separados. Sería un mal diseño unir dos cosas que cambian por diferentes razones en diferentes momentos.

El controlador no se preocupa por lo que está en la vista solo que cuando se llama a uno de sus métodos, proporciona datos específicos a la vista. Solo necesita saber acerca de la funcionalidad del modelo en la medida en que sepa que necesita llamar a los métodos que tendrán. No sabe nada más que eso.

Saber que un objeto tiene un método disponible para llamar no es lo mismo que conocer su funcionalidad.

Schleis
fuente
1
La razón por la que pensé que el controlador debería incluir métodos como specificButtonsPressed()es porque leí que la vista no debería saber nada sobre la funcionalidad de sus botones y otros elementos de la GUI. Me han enseñado que cuando se presiona un botón, la vista simplemente debe informar al controlador, y el controlador debe decidir "qué significa" (y luego invocar los métodos apropiados en el modelo). Hacer la llamada de vista controller.saveData()significa que la vista tiene que saber qué significa presionar este botón, además del hecho de que se presionó.
Aviv Cohn
1
Si abandono la idea de la separación completa entre presionar un botón y su significado (lo que resulta en que el controlador tenga métodos como specificButtonPressed()), de hecho, el controlador no estaría tan vinculado a la GUI. ¿Debo abandonar los specificButtonPressed()métodos? ¿La ventaja que creo que tiene estos métodos tiene sentido para usted? ¿O buttonPressed()no vale la pena tener métodos en el controlador?
Aviv Cohn
1
En otras palabras (perdón por los largos comentarios): Creo que la ventaja de tener specificButtonPressed()métodos en el controlador es que separa la Vista del significado de presionar completamente los botones . Sin embargo, la desventaja es que vincula el controlador a la GUI en cierto sentido. ¿Qué enfoque es mejor?
Aviv Cohn
@prog IMO El controlador debe estar ciego a la vista. Un botón tendrá algún tipo de funcionalidad, pero no necesita conocer los detalles del mismo. Solo necesita saber que está enviando algunos datos a un método de controlador. En ese sentido, el nombre no importa. Se puede llamar foo, o con la misma facilidad fireZeMissiles. Solo sabrá que debe informar a una función específica. No sabe qué hace la función, solo eso lo llamará. El controlador no está preocupado por cómo se invocan sus métodos, solo porque responderá de cierta manera cuando lo estén.
Schleis
1

La responsabilidad única de los controladores es ser el contrato que media entre la vista y el modelo. La vista solo debe ser responsable de la pantalla, el modelo solo debe ser responsable de la lógica empresarial. Es responsabilidad de los controladores unir esas dos responsabilidades.

Eso está muy bien, pero alejarse un poco de la academia; un controlador en MVC generalmente se compone de muchos métodos de acción más pequeños. Estas acciones generalmente corresponden a cosas que una cosa puede hacer. Si vendo productos, probablemente tendré un ProductController. Ese controlador tendrá acciones como GetReviews, ShowSpecs, AddToCart ect ...

La vista tiene el SRP de mostrar la interfaz de usuario, y parte de esa interfaz de usuario incluye un botón que dice AddToCart.

El controlador tiene el SRP de conocer todas las vistas y modelos involucrados en el proceso.

La acción AddToCart de los controladores tiene el SRP específico de conocer a todos los que deben participar cuando se agrega un artículo a un carrito.

El modelo del producto tiene el SRP de modelar la lógica del producto, y el modelo ShoppingCart tiene el SRP de modelar cómo se guardan los artículos para su posterior pago. El modelo de usuario tiene un SRP de modelado del usuario que agrega cosas a su carrito.

Puede y debe reutilizar modelos para hacer su negocio y esos modelos deben estar acoplados en algún momento de su código. El controlador controla cada forma única en que ocurre el acoplamiento.

WhiteleyJ
fuente
0

Los controladores en realidad solo tienen una responsabilidad: alterar el estado de las aplicaciones en función de la entrada del usuario.

Un controlador puede enviar comandos al modelo para actualizar el estado del modelo (por ejemplo, editar un documento). También puede enviar comandos a su vista asociada para cambiar la presentación de la vista del modelo (por ejemplo, desplazándose por un documento).

source: wikipedia

Si, en cambio, tiene "controladores" al estilo Rails (que hacen malabarismos con instancias de registro activas y plantillas tontas) , entonces, por supuesto, están rompiendo SRP.

Por otra parte, las aplicaciones de estilo Rails no son realmente MVC para empezar.

mefisto
fuente