¿Cómo estructurar adecuadamente un proyecto en winform?

26

Hace un tiempo comencé a crear una aplicación winform y en ese momento era pequeña y no pensé en cómo estructurar el proyecto.

Desde entonces, agregué características adicionales a medida que necesitaba y la carpeta del proyecto se está volviendo cada vez más grande y ahora creo que es hora de estructurar el proyecto de alguna manera, pero no estoy seguro de cuál es la forma correcta, así que tengo algunas preguntas.

¿Cómo reestructurar correctamente la carpeta del proyecto?

En este momento estoy pensando en algo como esto:

  • Crear carpeta para formularios
  • Crear carpeta para clases de utilidad
  • Crear carpeta para clases que contienen solo datos

¿Cuál es la convención de nomenclatura al agregar clases?

¿Debería también cambiar el nombre de las clases para que su funcionalidad se pueda identificar simplemente mirando su nombre? Por ejemplo, renombrar todas las clases de formularios, de modo que su nombre termine con Form . ¿O esto no es necesario si se crean carpetas especiales para ellos?

Qué hacer para que no todo el código del formulario principal termine en Form1.cs

Otro problema que encontré es que a medida que el formulario principal se vuelve más masivo con cada característica que agrego, el archivo de código (Form1.cs) se está volviendo realmente grande. Tengo, por ejemplo, un TabControl y cada pestaña tiene un montón de controles y todo el código terminó en Form1.cs. ¿Cómo evitar esto?

Además, ¿conoce algún artículo o libro que aborde estos problemas?

usuario850010
fuente

Respuestas:

24

Parece que has caído en algunos de los escollos comunes, pero no te preocupes, se pueden solucionar :)

Primero debe ver su aplicación de manera un poco diferente y comenzar a dividirla en trozos. Podemos dividir los trozos en dos direcciones. Primero podemos separar la lógica de control (las reglas de negocio, el código de acceso a datos, el código de derechos de usuario, todo ese tipo de cosas) del código de la interfaz de usuario. En segundo lugar, podemos dividir el código de la interfaz de usuario en fragmentos.

Entonces haremos la última parte primero, dividiendo la IU en trozos. La forma más fácil de hacer esto es tener un único formulario de host en el que compones tu interfaz de usuario con controles de usuario. Cada control de usuario estará a cargo de una región del formulario. Imagine que su aplicación tiene una lista de usuarios, y cuando hace clic en un usuario, un cuadro de texto debajo se llena con sus detalles. Puede tener un control de usuario que administre la visualización de la lista de usuarios y un segundo que administre la visualización de los detalles del usuario.

El verdadero truco aquí es cómo gestionar la comunicación entre los controles. No querrá 30 controles de usuario en el formulario, todos con referencias aleatorias entre sí y métodos de llamada.

Entonces creas una interfaz para cada control. La interfaz contiene las operaciones que el control aceptará y cualquier evento que genere. Cuando piensa en esta aplicación, no le importa si cambia la selección de la lista del cuadro de lista, le interesa el hecho de que un nuevo usuario haya cambiado.

Entonces, utilizando nuestra aplicación de ejemplo, la primera interfaz para el control que aloja el cuadro de lista de usuarios incluiría un evento llamado UserChanged que pasa un objeto de usuario.

Esto es genial porque ahora si te aburres del cuadro de lista y quieres un control de ojo mágico con zoom 3d, simplemente codifícalo en la misma interfaz y conéctalo :)

Ok, entonces la segunda parte, separando la lógica de la interfaz de usuario de la lógica del dominio. Bueno, este es un camino muy usado y te recomiendo que mires el patrón MVP aquí. Es muy simple

Cada control ahora se llama Vista (V en MVP) y ya hemos cubierto la mayor parte de lo que se necesita arriba. En este caso, el control y una interfaz para ello.

Todo lo que estamos agregando es el modelo y el presentador.

El modelo contiene la lógica que gestiona el estado de su aplicación. Ya sabes las cosas, iría a la base de datos para obtener los usuarios, escribir en la base de datos cuando agregas un usuario, y así sucesivamente. La idea es que puede probar todo esto en completo aislamiento de todo lo demás.

El presentador es un poco más complicado de explicar. Es una clase que se encuentra entre el modelo y la Vista. Es creado por la vista y la vista pasa al presentador usando la interfaz que discutimos anteriormente.

El presentador no tiene que tener su propia interfaz, pero me gusta crear una de todos modos. Hace que lo que quieres que haga el presentador sea explícito.

Por lo tanto, el presentador expondría métodos como ListOfAllUsers que la Vista usaría para obtener su lista de usuarios, alternativamente, podría colocar un método AddUser en la Vista y llamarlo desde el presentador. Prefiero el último. De esta forma, el presentador puede agregar un usuario al cuadro de lista cuando lo desee.

El presentador también tendría propiedades como CanEditUser, que devolverá verdadero si el usuario seleccionado se puede editar. La Vista consultará eso cada vez que necesite saber. Es posible que desee los editables en negro y los de solo lectura en gris. Técnicamente, esa es una decisión para la Vista, ya que está enfocada en la IU, si el usuario es editable en primer lugar es para el Presentador. El presentador sabe porque habla con el modelo.

En resumen, use MVP. Microsoft proporciona algo llamado SCSF (Smart Client Software Factory) que usa MVP de la manera que he descrito. Hace muchas otras cosas también. Es bastante complejo y no me gusta cómo lo hacen todo, pero puede ayudar.

Ian
fuente
8

Personalmente prefiero separar las diferentes áreas de preocupación entre varios ensamblajes en lugar de agrupar todo en un solo ejecutable.

Por lo general, prefiero mantener una cantidad mínima absoluta de código en el punto de entrada de la aplicación: sin lógica de negocios, sin código GUI y sin acceso a datos (bases de datos / acceso a archivos / conexiones de red / etc.); Normalmente limito el código del punto de entrada (es decir, el ejecutable) a algo parecido a

  • Crear e inicializar los diversos componentes de la aplicación a partir de todos los ensamblajes dependientes
  • Configurar cualquier componente de terceros del que depende toda la aplicación (por ejemplo, Log4Net para la salida de diagnóstico)
  • También probablemente incluiré un bit de código de "capturar todas las excepciones y registrar el rastro de la pila" en la función principal que ayudará a registrar las circunstancias de cualquier falla crítica / fatal imprevista.

En cuanto a los componentes de la aplicación en sí, generalmente busco al menos tres en una aplicación pequeña

  • Capa de acceso a datos (conexiones a la base de datos, acceso a archivos, etc.): dependiendo de la complejidad de los datos persistentes / almacenados utilizados por la aplicación, puede haber varios de estos conjuntos. Probablemente crearía un conjunto separado para el manejo de la base de datos (posiblemente incluso múltiples ensamblajes si la interacción con la base de datos implica algo complejo; por ejemplo, si está muy bien con una base de datos mal diseñada, es posible que deba manejar las relaciones de la base de datos en el código, por lo tanto, podría tener sentido escribir varios módulos para su inserción y recuperación)

  • Capa lógica: la "carne" principal que contiene todas las decisiones y algoritmos que hacen que su aplicación funcione. Estas decisiones no deberían saber absolutamente nada sobre la GUI (¿quién dice que hay una GUI?), Y no deberían saber absolutamente nada sobre la base de datos (¿Eh? ¿Hay una base de datos? ¿Por qué no un archivo?). Con suerte, una capa lógica bien diseñada se puede "extraer" y soltar en otra aplicación sin necesidad de volver a compilarla. En una aplicación complicada, puede haber un montón de estos conjuntos lógicos (porque es posible que solo desee extraer 'piezas' sin arrastrar el resto de la aplicación)

  • Capa de presentación (es decir, la GUI); En una aplicación pequeña, puede haber un solo "formulario principal" con un par de cuadros de diálogo que pueden ir en un solo ensamblaje; en una aplicación más grande, puede haber ensamblajes separados para partes funcionales completas de la GUI. Las clases aquí harán poco más que hacer que la interacción del usuario funcione; será poco más que un shell con alguna validación de entrada básica, manejo de cualquier animación, etc. Cualquier evento / clic de botón que "haga algo" se pasará a la capa lógica (por lo que mi capa de presentación no contendrá estrictamente ninguna lógica de aplicación, pero tampoco colocará la carga de ningún código GUI en la capa lógica tampoco, por lo que cualquier barra de progreso u otras cosas elegantes también se colocarán en el ensamblaje de presentación / ies)

Mi razón principal para dividir las capas Presentación, Lógica y Datos en conjuntos separados es la siguiente: creo que es preferible poder ejecutar la lógica de la aplicación principal sin su base de datos o su GUI.

Para decirlo de otra manera; si quiero escribir otro ejecutable que se comporte exactamente de la misma manera que su aplicación, pero que use una interfaz de línea de comandos o una interfaz web; y cambia el almacenamiento de la base de datos por el almacenamiento de archivos (o tal vez un tipo diferente de base de datos), luego puedo hacerlo sin tener que tocar la lógica de la aplicación principal; todo lo que necesito hacer es escribir una pequeña herramienta de línea de comandos y otro modelo de datos, luego "conéctelo todo", y estoy listo para comenzar.

Puede estar pensando "Bueno, nunca voy a querer hacer nada de eso, así que no importa si no puedo cambiar estas cosas". El punto real es que una de las características de una aplicación modular es capacidad de extraer 'fragmentos' (sin necesidad de volver a compilar nada) y reutilizar esos fragmentos en otro lugar. Para escribir código como este, generalmente lo obliga a pensar mucho sobre los principios de diseño: deberá pensar en escribir muchas más interfaces y pensar cuidadosamente sobre las compensaciones de varios principios SÓLIDOS (en el mismo como lo haría para el desarrollo impulsado por el comportamiento o TDD)

A veces, lograr esta separación de un fragmento de código monolítico existente es un poco doloroso y requiere mucha refactorización cuidadosa, eso está bien, debería poder hacerlo de forma incremental, incluso puede llegar a un punto donde hay demasiados conjuntos y decide volver al otro lado y comenzar a envolver las cosas nuevamente (ir demasiado lejos en la dirección opuesta puede tener el efecto de hacer que esos ensambles sean bastante inútiles por sí mismos)

Ben Cottrell
fuente
4

Según la estructura de la carpeta, lo que sugirió está bien en general. Es posible que deba agregar carpetas para los recursos (a veces las personas crean recursos de manera que cada conjunto de recursos se agrupe bajo un código de idioma ISO para admitir varios idiomas), imágenes, scripts de bases de datos, preferencias del usuario (si no se tratan como recursos), fuentes , dlls externos, dlls locales, etc.

¿Cuál es la convención de nomenclatura al agregar clases?

Por supuesto, desea separar cada clase fuera del formulario. También recomendaría un archivo por clase (aunque MS no hace eso en el código generado para EF, por ejemplo).

Muchas personas usan sustantivos cortos significativos en plural (por ejemplo, clientes). Algunos piden que el nombre esté cerca del nombre singular de la tabla de la base de datos correspondiente (si usa la asignación 1-1 entre objetos y tablas).

Para las clases de nomenclatura hay muchas fuentes, por ejemplo, eche un vistazo a: Convenciones de nomenclatura y normas de programación .net - Mejores prácticas y / o Pautas de codificación STOVF-C #

Qué hacer para que no todo el código del formulario principal termine en Form1.cs

El código auxiliar debe ir a una o más clases auxiliares. Otro código muy común a los controles de la GUI, como la aplicación de las preferencias del usuario a una cuadrícula, podría eliminarse del formulario y agregarse a las clases auxiliares o subclasificar el control en cuestión y crear los métodos necesarios allí.

Debido a la naturaleza de acción de eventos de MS Windows Forms, no hay nada que yo sepa que pueda ayudarlo a eliminar el código del formulario principal sin agregar ambigüedad y esfuerzo. Sin embargo, MVVM puede ser una opción (en proyectos futuros), ver por ejemplo: MVVM para Windows Forms .

Ninguna posibilidad
fuente
2

Considere MVP como una opción porque lo ayudará a organizar la lógica de presentación, que es todo en las aplicaciones de grandes empresas. En la vida real, la lógica de la aplicación reside principalmente en la base de datos, por lo que raramente tiene la necesidad de escribir una capa comercial en código nativo, dejándola solo con la necesidad de tener una funcionalidad de presentación bien estructurada.

La estructura similar a MVP dará como resultado un conjunto de presentadores o controladores, si lo prefiere, que se coordinarán entre sí y no se mezclarán con la interfaz de usuario o el código detrás de las cosas. Incluso podría usar diferentes interfaces de usuario con el mismo controlador.

Finalmente, la simple organización de recursos, el desacoplamiento de componentes y la especificación de dependencias con IoC y DI, junto con un enfoque MVP le proporcionarán las claves para construir un sistema que evite errores y complejidad comunes, se entregue a tiempo y esté abierto a cambios.

Panos Roditakis
fuente
1

La estructura de un proyecto depende totalmente del proyecto y su tamaño, sin embargo, puede agregar algunas carpetas, por ejemplo

  • Común (que contiene clases, por ejemplo, utilidades)
  • DataAccess (clases relacionadas con el acceso a datos usando sql o cualquier otro servidor de base de datos que esté usando)
  • Estilos (si tiene archivos CSS en su proyecto)
  • Recursos (por ejemplo, imágenes, archivos de recursos)
  • WorkFlow (Clases relacionadas con el flujo de trabajo si tiene alguna)

No necesita colocar formularios en ningún tipo de carpeta, simplemente cambie el nombre de sus formularios en consecuencia. Me refiero a su sentido común, nadie sabe qué nombre debe ser tu forma mejor que tú.

La convención de nomenclatura es como si su clase imprimiera un mensaje "Hola mundo", entonces el nombre de la clase debería estar relacionado con la tarea y el nombre apropiado de la clase debería ser HelloWorld.cs.

también puede crear regiones, p. ej.

#region Hello y luego endregional final.

Puede crear clases para pestañas, estoy bastante seguro de que puede hacerlo, y una última cosa es reutilizar su código siempre que sea posible, la mejor práctica es crear métodos y usarlos nuevamente cuando sea necesario.

Libros? erm.

No hay libros que le digan la estructura de los proyectos, ya que cada proyecto es diferente, usted aprende este tipo de cosas por experiencia.

Espero que haya ayudado!

Muhammad Raja
fuente