Como estruturar corretamente um projeto no winform?

26

Há um tempo atrás, comecei a criar um aplicativo winform e, na época, era pequeno e não pensava em como estruturar o projeto.

Desde então, adicionei recursos adicionais conforme necessário e a pasta do projeto está ficando cada vez maior e agora acho que é hora de estruturar o projeto de alguma forma, mas não tenho certeza de qual é o caminho correto, por isso tenho poucas perguntas.

Como reestruturar corretamente a pasta do projeto?

No momento, estou pensando em algo assim:

  • Criar pasta para formulários
  • Criar pasta para classes de utilitário
  • Criar pasta para classes que contêm apenas dados

Qual é a convenção de nomenclatura ao adicionar classes?

Devo também renomear classes para que sua funcionalidade possa ser identificada apenas olhando seu nome? Por exemplo, renomear todas as classes de formulários, para que seu nome termine com Form . Ou isso não é necessário se pastas especiais para eles forem criadas?

O que fazer, para que nem todo o código do formulário principal acabe no Form1.cs

Outro problema que encontrei é que, como o formulário principal está ficando mais massivo com cada recurso adicionado, o arquivo de código (Form1.cs) está ficando muito grande. Eu tenho, por exemplo, um TabControl e cada guia tem um monte de controles e todo o código acabou no Form1.cs. Como evitar isso?

Além disso, você conhece algum artigo ou livro que lida com esses problemas?

user850010
fonte

Respostas:

24

Parece que você caiu em algumas das armadilhas comuns, mas não se preocupe, elas podem ser corrigidas :)

Primeiro, você precisa olhar para seu aplicativo de maneira um pouco diferente e começar a dividi-lo em pedaços. Podemos dividir os pedaços em duas direções. Primeiro, podemos separar a lógica de controle (regras de negócios, código de acesso a dados, código de direitos do usuário, todo esse tipo de coisa) do código da interface do usuário. Segundo, podemos dividir o código da interface do usuário em partes.

Então, faremos a última parte primeiro, dividindo a interface do usuário em partes. A maneira mais fácil de fazer isso é ter um único formulário de host no qual você compõe sua interface do usuário com controles do usuário. Cada controle de usuário será responsável por uma região do formulário. Imagine que seu aplicativo tenha uma lista de usuários e, quando você clica em um usuário, uma caixa de texto abaixo é preenchida com os detalhes. Você pode ter um controle de usuário gerenciando a exibição da lista de usuários e um segundo gerenciando a exibição dos detalhes do usuário.

O verdadeiro truque aqui é como você gerencia a comunicação entre os controles. Você não deseja 30 controles de usuário no formulário, mantendo referências aleatórias entre si e chamando métodos sobre eles.

Então você cria uma interface para cada controle. A interface contém as operações que o controle aceitará e quaisquer eventos que ele gerar. Quando você pensa sobre este aplicativo, não se importa se a seleção da lista da caixa de listagem muda, você está interessado no fato de um novo usuário ter sido alterado.

Portanto, usando nosso aplicativo de exemplo, a primeira interface para o controle que hospeda a caixa de listagem de usuários incluiria um evento chamado UserChanged que distribui um objeto de usuário.

Isso é ótimo porque agora, se você se cansar da caixa de listagem e quiser um controle de olho mágico com zoom 3d, basta codificá-lo para a mesma interface e conectá-lo :)

Ok, então parte dois, separando a lógica da interface do usuário da lógica do domínio. Bem, este é um caminho bem usado e eu recomendo que você veja o padrão MVP aqui. É realmente simples.

Agora, cada controle é chamado de View (V no MVP) e já cobrimos a maior parte do necessário acima. Nesse caso, o controle e uma interface para ele.

Tudo o que estamos adicionando é o modelo e o apresentador.

O modelo contém a lógica que gerencia o estado do seu aplicativo. Você conhece o material, ele iria para o banco de dados para obter os usuários, escrever no banco de dados quando você adicionasse um usuário e assim por diante. A idéia é que você pode testar tudo isso em completo isolamento de todo o resto.

O apresentador é um pouco mais complicado de explicar. É uma classe que fica entre o modelo e a View. Ele é criado pela visualização e a visualização passa para o apresentador usando a interface que discutimos anteriormente.

O apresentador não precisa ter sua própria interface, mas eu gosto de criar uma de qualquer maneira. Torna explícito o que você deseja que o apresentador.

Portanto, o apresentador expôs métodos como ListOfAllUsers, que o View usaria para obter sua lista de usuários. Como alternativa, você pode colocar um método AddUser no View e chamar isso do apresentador. Eu prefiro o último. Dessa forma, o apresentador pode adicionar um usuário à caixa de listagem sempre que desejar.

O Presenter também teria propriedades como CanEditUser, que retornarão true se o usuário selecionado puder ser editado. O modo de exibição consultará isso sempre que precisar saber. Você pode querer editáveis ​​em preto e somente leitura em cinza. Tecnicamente, é uma decisão para o modo de exibição, pois é focado na interface do usuário, se o usuário é editável em primeiro lugar, é para o apresentador. O apresentador sabe porque fala com o modelo.

Então, em resumo, use o MVP. A Microsoft fornece algo chamado SCSF (Smart Client Software Factory), que usa MVP da maneira que descrevi. Também faz muitas outras coisas. É bastante complexo e não gosto da maneira como eles fazem tudo, mas pode ajudar.

Ian
fonte
8

Pessoalmente, prefiro separar diferentes áreas de preocupação entre vários assemblies, em vez de agrupar tudo em um único executável.

Normalmente, prefiro manter uma quantidade mínima absoluta de código no ponto de entrada do aplicativo - Sem lógica comercial, sem código da GUI e sem acesso a dados (bancos de dados / acesso a arquivos / conexões de rede / etc); Normalmente, limite o código do ponto de entrada (ou seja, o executável) a algo semelhante ao

  • Criando e inicializando os vários componentes de aplicativos de todos os conjuntos dependentes
  • Configurando componentes de terceiros dos quais todo o aplicativo depende (por exemplo, Log4Net para saída de diagnóstico)
  • Além disso, provavelmente incluirei um código do tipo "pegar todas as exceções e registrar o rastreamento de pilha" na função principal, que ajudará a registrar as circunstâncias de falhas críticas / fatais imprevistas.

Quanto aos próprios componentes do aplicativo, eu normalmente viso pelo menos três em um aplicativo pequeno

  • Camada de acesso a dados (conexões de banco de dados, acesso a arquivos etc.) - dependendo da complexidade de quaisquer dados persistentes / armazenados usados ​​pelo aplicativo, pode haver vários desses assemblies - eu provavelmente criaria um assembly separado para manipulação de banco de dados (possivelmente até vários assemblies se a interação com o banco de dados envolver algo complexo - por exemplo, se você é um estuque com um banco de dados mal projetado, pode precisar lidar com relacionamentos de banco de dados em código; portanto, faz sentido escrever vários módulos para inserção e recuperação)

  • Camada lógica - a principal "carne" que contém todas as decisões e algoritmos que fazem seu aplicativo funcionar. Essas decisões não devem saber absolutamente nada sobre a GUI (quem diz que existe uma GUI?) E não devem saber absolutamente nada sobre o banco de dados (Hein? Há um banco de dados? Por que não um arquivo?). Espera-se que uma camada lógica bem projetada possa ser "arrancada" e inserida em outro aplicativo sem precisar ser recompilada. Em um aplicativo complicado, pode haver um monte desses assemblies lógicos (porque você pode apenas extrair 'partes' sem arrastar o resto do aplicativo)

  • Camada de apresentação (isto é, a GUI); Em um aplicativo pequeno, pode haver apenas um "formulário principal" único com algumas caixas de diálogo que podem ir para um único conjunto - em um aplicativo maior, pode haver conjuntos separados para partes funcionais inteiras da GUI. As classes aqui farão pouco mais do que fazer a interação do usuário funcionar - será pouco mais que um shell com alguma validação básica de entrada, manipular qualquer animação, etc. Quaisquer cliques de eventos / botões que "façam alguma coisa" serão transmitidos para a camada lógica (portanto, minha camada de apresentação não conterá estritamente nenhuma lógica de aplicativo, mas também não sobrecarregará nenhum código da GUI na camada lógica - portanto, qualquer barra de progresso ou outras coisas sofisticadas também estarão presentes na montagem da apresentação / s)

Minha principal justificativa para dividir as camadas Apresentação, Lógica e Dados para separar montagens é a seguinte: eu sinto que é preferível poder executar a lógica principal do aplicativo sem seu banco de dados ou sua GUI.

Em outras palavras; se eu quiser escrever outro executável que se comporte exatamente da mesma maneira que seu aplicativo, mas use uma interface de linha de comando ou uma interface da web; e troca o armazenamento do banco de dados pelo armazenamento de arquivos (ou talvez um tipo diferente de banco de dados), posso fazê-lo sem precisar tocar na lógica principal do aplicativo - tudo o que preciso fazer é escrever um pouco uma ferramenta de linha de comando e outro modelo de dados, depois "conecte tudo" e estou pronto para começar.

Você pode estar pensando "Bem, eu nunca vou querer fazer nada disso, então não importa se não posso trocar essas coisas" - o ponto real é que uma das características de um aplicativo modular é a capacidade de extrair 'pedaços' (sem precisar recompilar nada) e reutilizar esses pedaços em outro lugar. Para escrever um código como esse, geralmente obriga a pensar bastante sobre os princípios de design - você precisará pensar em escrever muito mais interfaces e pensar cuidadosamente nas vantagens e desvantagens de vários princípios do SOLID (da mesma forma da mesma forma que você faria para o Behavior-Driven-Development ou TDD)

Às vezes, conseguir essa separação de um bloco monolítico de código existente é um pouco doloroso e requer muita refatoração cuidadosa - tudo bem, você deve poder fazer isso de forma incremental - você pode até chegar a um ponto em que há muitas montagens e decidir voltar para o outro lado e começar a juntar as coisas novamente (ir longe demais na direção oposta pode ter o efeito de tornar essas assembléias inúteis por conta própria)

Ben Cottrell
fonte
4

De acordo com a estrutura da pasta, o que você sugeriu é OK em geral. Pode ser necessário adicionar pastas para recursos (às vezes as pessoas criam recursos para que cada conjunto de recursos seja agrupado sob um código de idioma ISO para oferecer suporte a vários idiomas), imagens, scripts de banco de dados, preferências do usuário (se não tratadas como recursos), fontes , DLLs externas, DLLs locais, etc.

Qual é a convenção de nomenclatura ao adicionar classes?

Obviamente, você deseja separar cada classe fora do formulário. Eu recomendaria também um arquivo por classe (embora o MS não faça isso no código gerado para EF, por exemplo).

Muitas pessoas usam substantivos curtos significativos no plural (por exemplo, Clientes). Alguns sugerem que o nome esteja próximo ao nome singular da tabela de banco de dados correspondente (se você usar o mapeamento 1-1 entre objetos e tabelas).

Para nomear classes, existem muitas fontes, por exemplo, consulte: Convenções de nomenclatura .net e padrões de programação - Melhores práticas e / ou diretrizes de codificação STOVF-C #

O que fazer, para que nem todo o código do formulário principal acabe no Form1.cs

O código auxiliar deve ir para uma ou mais classes auxiliares. Outro código que é muito comum nos controles da GUI, como a aplicação de preferências do usuário em uma grade, pode ser removido do formulário e adicionado às classes auxiliares ou subclassificando o controle em questão e criando os métodos necessários.

Devido à natureza de ação do evento do MS Windows Forms, não há nada que eu saiba que possa ajudá-lo a remover o código do formulário principal sem adicionar ambiguidade e esforço. No entanto, o MVVM pode ser uma opção (em projetos futuros), consulte, por exemplo: MVVM para Windows Forms .

NoChance
fonte
2

Considere o MVP como uma opção, pois ajudará a organizar a lógica da apresentação, que é tudo em aplicativos de negócios de grande porte. Na vida real, a lógica do aplicativo reside principalmente no banco de dados; portanto, você raramente precisa escrever uma camada de negócios no código nativo, deixando apenas a necessidade de ter uma funcionalidade de apresentação bem estruturada.

A estrutura do tipo MVP resultará em um conjunto de apresentadores ou controladores, se você preferir, que se coordenarão entre si e não se misturarão à interface do usuário ou ao código por trás das coisas. Você pode até usar UIs diferentes com o mesmo controlador.

Por fim, recursos simples de organização, dissociação de componentes e especificação de impedâncias com IoC e DI, juntamente com uma abordagem MVP, fornecerão as chaves para a construção de um sistema que evita erros e complexidade comuns, são entregues a tempo e estão abertos a alterações.

Panos Roditakis
fonte
1

A estrutura de um projeto depende totalmente do projeto e de seu tamanho; no entanto, você pode adicionar algumas pastas, por exemplo

  • Comum (contendo classes, por exemplo, Utilitários)
  • DataAccess (classes relacionadas ao acesso de dados usando sql ou qualquer outro servidor de banco de dados que você esteja usando)
  • Estilos (se você tiver algum arquivo CSS em seu projeto)
  • Recursos (por exemplo, imagens, arquivos de recursos)
  • Fluxo de Trabalho (Classes relacionadas ao fluxo de trabalho, se houver)

Você não precisa colocar formulários em nenhum tipo de pasta, apenas renomeie seus formulários de acordo. Quero dizer, é senso comum que ninguém sabe qual nome deve ser melhor para suas formas do que para si mesmo.

A convenção de nomenclatura é como se a sua classe imprimisse uma mensagem "Hello World", o nome da classe deveria ser algo relacionado à tarefa e o nome apropriado da classe deveria HelloWorld.cs.

você pode criar regiões também, por exemplo

#region Hello e depois endregionno final.

Você pode criar classes para guias, tenho certeza de que pode, e uma última coisa é reutilizar seu código sempre que possível, a melhor prática é criar métodos e usá-los novamente quando necessário.

Livros? erm.

Não há livros que informam a estrutura dos projetos, pois cada projeto é diferente, você aprende esse tipo de coisa por experiência.

Espero que tenha ajudado!

Muhammad Raja
fonte