Разделение пользовательского интерфейса и логики в C#

У кого-нибудь есть какие-либо советы по недопущению логики в моих классах GUI? Я стараюсь использовать хороший дизайн классов и держу их как можно дальше друг от друга, но мои классы Form обычно заканчиваются тем, что в них смешано больше не-пользовательского интерфейса, чем хотелось бы, и это приводит к тому, что обслуживание становится настоящей болью.

(Visual Studio 2008 Professional, C#, приложения для Windows).

Большое спасибо.

8 ответов

Решение

Поместите свою логику в отдельную сборку; и собрать эту сборку без ссылки на какие-либо пакеты графического интерфейса (например, System.Drawing, System.Windows.Forms, так далее.).

Это действительно просто вопрос практики и самодисциплины. Я имею в виду, мы все сделали это. И мы все время от времени продолжаем делать это в неправильных условиях (менеджер / клиент кричит, чтобы что-то было сделано "прямо сейчас" против "правильно" и т. Д.).

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

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

Трехслойная архитектура - это то, что вы ищете.

Вы строите 2 многоразовых слоя:

  • Уровень доступа к данным (DAL), который содержит только код, необходимый для чтения / записи из базы данных
  • Уровень бизнес-логики (BLL), который использует DAL, содержит бизнес-правила, проверку и предоставляет интерфейс для использования пользовательского интерфейса

Затем в вашем UI-проекте вы ссылаетесь на повторно используемые слои и обрабатываете только специфичные для UI вещи. Проект пользовательского интерфейса общается только с BLL, без прямой связи с DAL:

UI <---> BLL <---> DAL

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

Вы должны посмотреть на шаблоны проектирования, такие как:

Model-View-Controller (MVC), часто используемый веб-сайтами (ASP.NET)
Модель-представление-представление-модель (MVVM), часто используемая WPF

Соблюдая один из них, вы сможете разделить различные части вашего приложения.

Есть и другие модели, которые делают похожую работу.

Кроме того, разработка с использованием WPF может помочь, поскольку пользовательский интерфейс определяется XAML, а код, выполняющий эту работу, - это C#. Это может обеспечить базовую степень разделения. Если вы обнаружите, что пишете код на C#, который просто манипулирует пользовательским интерфейсом, вы можете сделать шаг назад и подумать: "Должен ли я сделать это в XAML?". Очевидно, что в коде есть вещи, которые вы должны сделать, но это только начало.

Узнайте, как написать классы контроллера, которые могут быть привязаны к форме данных, и как выполнить привязку данных. В WinForms это в основном сводится к интерфейсам INotifyPropertyChanged и IDataErrorInfo в классе контроллера и к использованию экземпляров BindingSource в классе формы.

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

Это основа всех конструкций MVC/MVVM.

Херби

Обычно в таких ситуациях; Я создаю вспомогательный класс методов, который выполняет всю тяжелую работу.

Что касается сохранения логики отдельно; определите, какие компоненты являются ключевыми, и перенесите их в класс вспомогательных методов. Например; если я обрабатываю GridView для обновления записей в зависимости от того, выбраны они или нет; и, если они есть, обновите ShipDate, в форме; я бы сначала выяснил, выбрана ли строка; затем извлеките поле Id, затем ShipDate, а затем передайте Id и ShipDate в метод моего вспомогательного класса, который выполняет всю работу.

Модульные тесты могут быть вашими друзьями здесь; в основном, если у вас есть какой-нибудь код, который делает вещи типа "логика"; у него должен быть модульный тест. Если это в классах GUI; это сложно проверить; однако, как только вы реорганизуете его; модульный тест должен быть тривиальным.

Вы должны посмотреть на следующие шаблоны:

MVC (Model-View-Controller) MVVM (Model-View-View-Model) - в основном используется в WPF с его богатой поддержкой привязки данных. MVP (Model-View-Presenter) - часто используется для WinForms и веб-приложений (из-за представления без сохранения состояния)

Прочтите этот пост в блоге, в котором приведен пример использования MVP для включения веб-интерфейса и представления WinForms с одним докладчиком: http://www.cerquit.com/blogs/post/MVP-Part-I-e28093-Building-it-from-Scratch.aspx

Кроме того, в следующем посте здесь описывается использование шаблона MVP для модульного тестирования вашей бизнес-логики: http://www.cerquit.com/blogs/post/Model-View-Presenter-Part-II---Unit-Testing.aspx

Одним словом, это называется рефакторинг.

Есть только несколько причин для вставки кода в пользовательский интерфейс:

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

Весь другой код "бизнес-логики" входит в другой класс, называемый классом бизнес-логики. Весь код взаимодействия с БД переходит в другой класс, называемый классом доступа к данным.

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

Ознакомьтесь с некоторыми книгами Мартина Фаулера по рефакторингу, например, "Рефакторинг: улучшение дизайна существующего кода". Другое модное слово - разделение проблем. Я знаю, что вы можете делать все это в одном классе, но код становится намного более читабельным и его легче отлаживать, когда он разделен на классы, как я описал выше.

Другие вопросы по тегам