Как правильно структурировать проект в winform?

26

Некоторое время назад я начал создавать приложение winform, и тогда оно было небольшим, и я не думал о том, как структурировать проект.

С тех пор я добавлял дополнительные функции по мере необходимости, и папка проекта становилась все больше и больше, и теперь я думаю, что пришло время каким-то образом структурировать проект, но я не уверен, как это правильно, поэтому у меня мало вопросов.

Как правильно реструктурировать папку проекта?

На данный момент я думаю о чем-то вроде этого:

  • Создать папку для форм
  • Создать папку для служебных классов
  • Создать папку для классов, которые содержат только данные

Что такое соглашение об именах при добавлении классов?

Должен ли я также переименовать классы, чтобы их функциональность можно было определить, просто взглянув на их имя? Например, переименование всех классов форм, чтобы их имя заканчивалось на Form . Или это не обязательно, если для них созданы специальные папки?

Что делать, чтобы не весь код основной формы оказался в Form1.cs

Другая проблема, с которой я столкнулся, заключается в том, что, поскольку основная форма с каждой добавляемой функцией становится все массивнее, файл кода (Form1.cs) становится действительно большим. У меня есть, например, TabControl, и каждая вкладка имеет кучу элементов управления, и весь код оказался в Form1.cs. Как этого избежать?

Кроме того, знаете ли вы какие-либо статьи или книги, которые касаются этих проблем?

user850010
источник

Ответы:

24

Похоже, вы попали в некоторые из распространенных ошибок, но не волнуйтесь, их можно исправить :)

Сначала вам нужно посмотреть на свое приложение немного по-другому и начать разбивать его на куски. Мы можем разделить куски в двух направлениях. Сначала мы можем отделить управляющую логику (бизнес-правила, код доступа к данным, код прав пользователя и все такое) от кода пользовательского интерфейса. Во-вторых, мы можем разбить код пользовательского интерфейса на куски.

Итак, сначала мы выполним последнюю часть, разбив пользовательский интерфейс на куски. Самый простой способ сделать это - создать единую форму хоста, на которой вы создадите свой пользовательский интерфейс с помощью пользовательских элементов управления. Каждый пользовательский элемент управления будет отвечать за область формы. Итак, представьте, что в вашем приложении есть список пользователей, и когда вы нажимаете на пользователя, текстовое поле под ним заполняется его данными. Вы могли бы иметь один пользовательский элемент управления, управляющий отображением списка пользователей, и второй, управляющий отображением данных пользователя.

Настоящая хитрость здесь заключается в том, как вы управляете связью между элементами управления. Вам не нужно, чтобы 30 пользовательских элементов управления в форме произвольно содержали ссылки друг на друга и вызывали методы для них.

Таким образом, вы создаете интерфейс для каждого элемента управления. Интерфейс содержит операции, которые примет элемент управления, и любые события, которые он вызывает. Когда вы думаете об этом приложении, вам все равно, изменится ли выбор списка в списке, вы заинтересованы в том, чтобы изменился новый пользователь.

Поэтому, используя наше приложение-пример, первый интерфейс для элемента управления, в котором размещается список пользователей, будет включать в себя событие UserChanged, которое передает объект пользователя.

Это здорово, потому что теперь, если вам надоест список, и вам нужен волшебный глазной контроль с 3d масштабированием, вы просто кодируете его в тот же интерфейс и подключаете его :)

Итак, часть вторая, отделяя логику пользовательского интерфейса от логики домена. Ну, это хорошо проработанный путь, и я бы посоветовал вам взглянуть на схему MVP здесь. Это действительно просто.

Каждый элемент управления теперь называется представлением (V в MVP), и мы уже рассмотрели большую часть того, что необходимо выше. В этом случае управление и интерфейс для него.

Все, что мы добавляем, это модель и ведущий.

Модель содержит логику, которая управляет состоянием вашего приложения. Вы знаете материал, он будет идти в базу данных, чтобы получить пользователей, записывать в базу данных при добавлении пользователя и так далее. Идея в том, что вы можете проверить все это в полной изоляции от всего остального.

Ведущий немного сложнее объяснить. Это класс, который находится между моделью и представлением. Он создается представлением, и представление переходит в презентатор, используя интерфейс, который мы обсуждали ранее.

У докладчика не должно быть собственного интерфейса, но мне все равно нравится его создавать. Делает то, что вы хотите, чтобы докладчик делал явно.

Таким образом, докладчик будет предоставлять такие методы, как ListOfAllUsers, которые View будет использовать для получения списка пользователей. В качестве альтернативы, вы можете добавить метод AddUser для View и вызвать его из презентатора. Я предпочитаю последнее. Таким образом, докладчик может добавить пользователя в список, когда он захочет.

Presenter также будет иметь такие свойства, как CanEditUser, который будет возвращать true, если выбранный пользователь может быть отредактирован. Затем представление будет запрашивать это каждый раз, когда ему нужно знать. Вы могли бы хотеть редактируемые в черном и читать только в сером. Технически это решение для View, поскольку оно ориентировано на пользовательский интерфейс, независимо от того, редактируется ли пользователь в первую очередь, для Presenter. Ведущий знает, потому что он разговаривает с моделью.

Итак, в заключение, используйте MVP. Microsoft предоставляет что-то под названием SCSF (Smart Client Software Factory), которая использует MVP, как я описал. Это также делает много других вещей. Это довольно сложно, и мне не нравится, как они все делают, но это может помочь.

Ян
источник
8

Лично я предпочитаю разделять различные проблемные области между несколькими сборками, а не объединять все в один исполняемый файл.

Как правило, я предпочитаю хранить абсолютно минимальное количество кода в точке входа приложения - нет бизнес-логики, нет кода GUI и нет доступа к данным (базы данных / доступ к файлам / сетевые подключения / и т. Д.); Я обычно ограничиваю код точки входа (т.е. исполняемый файл) чем-то вроде

  • Создание и инициализация различных компонентов приложения из всех зависимых сборок
  • Настройка любых сторонних компонентов, от которых зависит все приложение (например, Log4Net для диагностического вывода)
  • также, вероятно, я включу в основной функции код типа «перехватывать все исключения и записывать трассировку стека», который поможет регистрировать обстоятельства любых непредвиденных критических / фатальных сбоев.

Что касается самих компонентов приложения, я обычно стремлюсь как минимум к трем в небольшом приложении

  • Уровень доступа к данным (соединения с базой данных, доступ к файлам и т. Д.) - в зависимости от сложности любых постоянных / хранимых данных, используемых приложением, может быть несколько таких сборок - возможно, я бы создал отдельную сборку для обработки базы данных (возможно, даже несколько сборки, если во взаимодействии с базой данных было что-то сложное - например, если вы застряли в плохо спроектированной базе данных, вам может понадобиться обрабатывать отношения БД в коде, поэтому может иметь смысл написать несколько модулей для вставки и извлечения)

  • Logic Layer - главное «мясо», содержащее все решения и алгоритмы, которые заставляют ваше приложение работать. Эти решения должны абсолютно ничего не знать о GUI (кто говорит, что есть GUI?) И абсолютно ничего не должны знать о базе данных (Ха? Есть база данных? Почему не файл?). Надеемся, что хорошо продуманный логический уровень можно «вырвать» и поместить в другое приложение без необходимости перекомпиляции. В сложном приложении может быть целая куча этих логических сборок (потому что вы можете просто хотеть вырывать «кусочки», не таща за собой остальную часть приложения)

  • Уровень представления (то есть GUI); В небольшом приложении может быть только одна «основная форма» с парой диалоговых окон, которые могут входить в одну сборку - в более крупном приложении могут быть отдельные сборки для целых функциональных частей GUI. Классы здесь сделают немного больше, чем просто взаимодействие с пользователем - это будет всего лишь оболочка с некоторой базовой проверкой ввода, обработкой любой анимации и т. Д. Любые события / нажатия кнопок, которые «что-то делают», будут переданы вперед. логический уровень (так что мой уровень представления строго не будет содержать логику приложения вообще, но он также не будет нести бремя любого кода GUI на уровне логики - так что любые индикаторы выполнения или другие причудливые вещи также будут находиться в сборке презентации / х годов)

Мое основное обоснование для разделения уровней Presentation, Logic и Data на отдельные сборки состоит в следующем: я чувствую, что желательно иметь возможность запускать основную логику приложения без вашей базы данных или вашего графического интерфейса.

Другими словами; если я хочу написать другой исполняемый файл, который ведет себя точно так же, как ваше приложение, но использует интерфейс командной строки или веб-интерфейс; и заменяет хранилище базы данных на хранилище файлов (или, может быть, другой тип базы данных), тогда я могу сделать это, не затрагивая основную логику приложения - все, что мне нужно сделать, это написать небольшой инструмент командной строки и другая модель данных, затем «подключи все это вместе», и я готов к работе.

Вы можете подумать: «Ну, я никогда не захочу делать что-либо из этого, поэтому не имеет значения, смогу ли я обмениваться этими вещами» - реальная точка зрения состоит в том, что одним из отличительных признаков модульного приложения является способность извлекать «чанки» (без необходимости что-либо перекомпилировать) и повторно использовать эти чанки в другом месте. Для написания такого кода, как правило, это заставляет вас долго и усердно думать о принципах проектирования - вам нужно будет подумать о написании гораздо большего количества интерфейсов и тщательно продумать компромиссы различных принципов SOLID ( так же, как и для Behavior-Driven-Development или TDD)

Иногда достижение такого отделения от существующего монолитного куска кода является немного болезненным и требует большого тщательного рефакторинга - это нормально, вы должны иметь возможность делать это постепенно - вы можете даже достичь точки, где слишком много сборок, и вы решаете вернуться в другую сторону и начать снова сворачивать вещи вместе (если зайти слишком далеко в противоположном направлении, это может сделать эти сборки довольно бесполезными сами по себе)

Бен Коттрелл
источник
4

Что касается структуры папок, то, что вы предложили, в целом нормально. Вам может понадобиться добавить папки для ресурсов (иногда люди создают ресурсы таким образом, чтобы каждый набор ресурсов группировался под языковым кодом ISO для поддержки нескольких языков), изображения, сценарии базы данных, пользовательские настройки (если не рассматриваются как ресурсы), шрифты , внешние библиотеки, локальные библиотеки и т. д.

Что такое соглашение об именах при добавлении классов?

Конечно, вы хотите отделить каждый класс от формы. Я бы также порекомендовал файл для каждого класса (хотя MS не делает этого в коде, сгенерированном для EF, например).

Многие люди используют значимые короткие существительные во множественном числе (например, Клиенты). Некоторые вводят имя, чтобы быть близким к единственному имени для соответствующей таблицы базы данных (если вы используете отображение 1-1 между объектами и таблицами).

Для классов имен существует множество источников, например, взгляните на: .net Соглашения об именах и стандарты программирования - Лучшие практики и / или STOVF-C # Рекомендации по кодированию

Что делать, чтобы не весь код основной формы оказался в Form1.cs

Вспомогательный код должен идти к одному или нескольким вспомогательным классам. Другой код, который очень распространен для элементов управления GUI, например, применяет пользовательские настройки к сетке, можно удалить из формы и либо добавить к вспомогательным классам, либо подклассифицировать рассматриваемый элемент управления и создать там необходимые методы.

Из-за природы событий-действий в MS Windows Forms, я ничего не знаю о том, что могло бы помочь вам убрать код из основной формы, не добавляя двусмысленности и усилий. Однако MVVM может быть выбором (в будущих проектах), см., Например: MVVM для Windows Forms .

Без шансов
источник
2

Рассматривайте MVP как вариант, поскольку он поможет вам организовать логику представления, которая есть во всех крупных бизнес-приложениях. В реальной жизни логика приложения находится в основном в базе данных, поэтому очень редко возникает необходимость в реальной записи бизнес-уровня в нативный код, оставляя только необходимость иметь хорошо структурированную функциональность представления.

MVP-подобная структура приведет к набору презентаторов или контроллеров, если вы предпочитаете, которые будут координироваться друг с другом и не будут смешиваться с пользовательским интерфейсом или кодом позади чего-либо. Вы даже можете использовать разные интерфейсы с одним и тем же контроллером.

Наконец, простая организация ресурсов, разделение компонентов и указание зависимостей с помощью IoC и DI, а также подход MVP предоставят вам ключи для построения системы, позволяющей избежать распространенных ошибок и сложностей, своевременно доставленной и открытой для изменений.

Панос Родитакис
источник
1

Структура проекта полностью зависит от проекта и его размера, однако вы можете добавить несколько папок, например:

  • Общий (содержащий классы, например, Утилиты)
  • DataAccess (классы, связанные с доступом к данным с использованием sql или любого другого сервера базы данных, который вы используете)
  • Стили (если у вас есть CSS-файлы в вашем проекте)
  • Ресурсы (например, изображения, файлы ресурсов)
  • WorkFlow (классы, связанные с рабочим процессом, если у вас есть)

Вам не нужно помещать формы в любую папку, просто переименуйте формы соответствующим образом. Я имею в виду здравый смысл, никто не знает, какое имя должно быть твоей формы лучше, чем ты сам.

Соглашение об именах похоже на то, что если ваш класс печатает сообщение «Hello World», тогда имя класса должно быть чем-то связанным с задачей, а соответствующее имя класса - HelloWorld.cs.

Вы можете создавать регионы, например,

#region Hello а затем endregionв конце.

Вы можете создавать классы для вкладок, я уверен, что можете, и последнее, что возможно, - это повторное использование вашего кода, где это возможно. Лучшая практика - создавать методы и использовать их снова, когда это необходимо.

Книги? эм.

Там нет книг, рассказывающих о структуре проектов, так как каждый проект индивидуален, вы изучаете такие вещи на собственном опыте.

Надеюсь, это помогло!

Мухаммед Раджа
источник