¿Cómo puedo aplicar el patrón MVC a una aplicación C # WinForms?

11

Soy un desarrollador de C ++ que ha estado usando el patrón MVC para diseñar GUI desde entonces.

Recientemente quería volver a C #, y configuré una aplicación de formularios Windows Forms, pero ahora estoy un poco perdido sobre cómo llevarlo a una estructura compatible con MVC.

Lo que estoy tratando de hacer actualmente es "declarar" la clase que se me da para WinForms como una vista, y agregar una clase para el modelo y el controlador en segundo plano. Sin embargo, no estoy realmente seguro de cómo interactuar con los eventos, como hacer clic en un botón. Por lo general, redirigiría estos eventos al controlador y realizaría una acción en la Vista una vez que haya terminado.

Sin embargo, esto se siente bastante insatisfactorio en esta constelación. Por ejemplo, si quisiera implementar un botón "Salir", tendría que redirigir el evento desde la Vista al Controlador, así como implementar un método público adicional en mi Vista que luego se puede llamar desde el Controlador, mientras yo podría simplemente llamar a Close () desde la Vista en primera instancia.

¿Tiene algún consejo para mí? ¿Mi comprensión de Windows Forms en C # simplemente no es lo suficientemente buena como para probar una implementación de MVC? ¿Estoy dando a la clase de formularios un papel incorrecto? ¿MVC es simplemente una arquitectura inapropiada para este caso de uso?

Sossenbinder
fuente
1
Pregunta del AT ¿Pero por qué WInforms? WPF estaba destinado a reemplazar Winforms y es compatible con MVC (Bueno, técnicamente MVVM). Aunque diré que la curva de aprendizaje de WPF puede ser empinada. (pero si lo haces mal puedes hacer que el código WPF se parezca a Winforms)
Peter M
1
@PeterM: porque incluso después de 10 años, WPF apesta y sigue siendo lento.
whatsisname
3
Parafraseando el comentario de @ whatsisname, WPF está orientado a aplicaciones más grandes. Las aplicaciones más pequeñas podrían ser mejor servidas con Winforms porque Winforms es posiblemente más simple. Sin embargo, si desea hacer ese argumento, también podría argumentar que su aplicación Winforms es lo suficientemente pequeña donde probablemente no necesite MVP de todos modos. MVVM se cuece en WPF. WPF tiene gráficos basados ​​en vectores, por lo que no sufre los mismos problemas de tamaño que Winforms. WPF es muy composable (puede poner fácilmente controles dentro de otros controles), y tiene un enlace de datos asesino. ¿Que es no gustar?
Robert Harvey
3
@whatsisname WPF puede apestar, pero apesta mucho menos que Winforms para cualquier cosa que supere un programa de juguetes
Peter M
2
Para los programas de escritorio internos, lo diría. Pero muchas compañías prefieren aplicaciones basadas en navegador para sus operaciones comerciales, simplemente porque no se requiere instalación.
Robert Harvey

Respuestas:

3

Casualmente, estoy trabajando en un proyecto de WinForms que se basa en MVC. No llamaría a esto una respuesta perfecta, pero explicaré mi diseño general y espero que esto pueda ayudarlo a encontrar el suyo.

Según la lectura que hice antes de comenzar este proyecto, parece que no hay una forma "correcta" de implementar esto. Seguí principios de diseño simples de OOP y MVC y el resto fue prueba y error a medida que desarrollé un flujo de trabajo.

¿MVC es simplemente una arquitectura inapropiada para este caso de uso?

No ..? No hay suficiente contexto en su pregunta para proporcionar una respuesta directa a eso. ¿Por qué estás usando MVC en primer lugar? ¿Cuáles son los requisitos no funcionales de su proyecto? ¿Tu proyecto va a ser muy pesado? ¿Te importa más la seguridad y prefieres una arquitectura en capas? ¿Cuáles son los principales componentes de su proyecto? Quizás cada componente necesita un patrón de diseño diferente. Averigüe por qué quiere usar este patrón de diseño en primer lugar y puede responder a su propia pregunta;)

Mi razón para usar MVC: es un patrón de diseño bastante simple de entender en mi opinión y mi diseño se basa en gran medida en la interacción del usuario. La forma en que MVC permite que el desarrollador separe las preocupaciones también es suficiente para mi aplicación. Esto hace que mi código sea mucho más fácil de mantener y probar.

También supongo que estoy usando más un diseño híbrido. Por lo general, el concepto ideal presentado en la ingeniería de software en realidad no se desarrolla en la práctica. Puede modificar el diseño para adaptarlo a las necesidades de su proyecto. No es necesario quedar atrapado en lo que está bien o mal. Hay prácticas generales, pero las reglas siempre se pueden doblar o romper siempre que no te dispares en el pie.

Mi implementación comenzó con un diseño de alto nivel que me dio una idea de qué componentes necesitaré. Es mejor comenzar de esta manera y avanzar en la arquitectura. Aquí está el diagrama del paquete para el proyecto (creado en StarUML): ingrese la descripción de la imagen aquí

Observe que cada capa, excepto la capa de presentación, depende del sistema de mensajería. Este es un "lenguaje" común que las capas inferiores y los subsistemas de esas capas usan para comunicarse entre sí. En mi caso, fue una enumeración simple basada en operaciones que se pueden realizar. Lo que me lleva al siguiente punto ...

Piense en las operaciones o comandos como la base para su implementación. ¿Qué quieres que haga tu aplicación? Divídalo en las operaciones más fundamentales. Por ejemplo: CreateProject, WriteNotes, SaveProject, LoadProject, etc. La GUI (o las clases Form) tendrán algún evento (como presionar un botón). Cada operación tiene un método de controlador asociado. En este caso, algo como Salir es demasiado simple. La aplicación simplemente se puede cerrar desde la clase Form. ¿Pero supongamos que primero quisiera conservar algunos datos de la aplicación en un archivo? Llamaré al método "Guardar" desde la clase de controlador correspondiente dentro de mi método de presionar un botón.

A partir de ahí, el controlador llamará al conjunto correcto de operaciones desde las clases de Servicio. Las clases de servicio en mi aplicación actúan como una interfaz para la capa de dominio. Validarán la entrada recibida de la llamada al método del controlador (y, por lo tanto, de la GUI) y manipularán el modelo de datos.

Una vez que se completa la validación y la manipulación de objetos correspondiente, el método de servicio devolverá un código de mensaje al controlador. Por ejemplo, MessageCodes.SaveSuccess. Tanto el controlador como las clases de servicio se basaron en los objetos de dominio y / o el conjunto general de operaciones que se pueden agrupar.

Por ejemplo: FileMenuController(operaciones: NewProject, SaveProject, LoadProject) -> ProjectServices(CreateProject, PersistProjectToFile, LoadProjectFromFile). Dónde Projectsería una clase de dominio en su modelo de datos. Las clases de controlador y servicio en mi caso eran clases no instanciables con métodos estáticos.

Luego, el controlador reconoce que la operación se completó sin éxito. Ahora, el controlador tiene su propio sistema de mensajería que utiliza para interactuar con la capa de presentación, de ahí la doble dependencia entre las capas de Servicio y Presentación. En este caso, el controlador siempre devuelve una clase llamada ViewStateen el ViewModelspaquete a la GUI. Este estado contiene información como: "¿ es válido el estado en el que intentó poner la aplicación? ", " Un mensaje legible por humanos sobre la operación que intentó realizar y por qué tuvo éxito o no (mensajes de error) " y una ViewModelclase.

La ViewModelclase contiene datos relevantes de la capa de dominio que la GUI usará para actualizar la vista. Estos modelos de vista se parecen a las clases de dominio, pero en mi caso usé objetos muy delgados . Básicamente no tienen prácticamente ningún comportamiento, solo transmiten información sobre el estado de nivel inferior de la aplicación. En otras palabras, NUNCA voy a regalar mis clases de dominio a la capa de presentación. Esta es también la razón por la cual los paquetes Controllersy Servicesdividen la capa de servicio en dos partes. Los controladores nunca manejarán clases de dominio ni validarán su estado. Simplemente actúan como un límite que convierte los datos relevantes de la GUI en datos relevantes del dominio que los servicios pueden usar y viceversa. La inclusión de la lógica de servicio en el controlador conduciría a una gran carga controladores, que son más difíciles de mantener.

Espero que esto te dé un punto de partida.

Topacio seductor
fuente