Какую базовую архитектуру я могу использовать для однослойного приложения Windows Forms? - PullRequest
1 голос
/ 02 марта 2012

У меня очень простое приложение для Windows Forms для сбора данных и составления отчетов по микрозаймам.Я разработал довольно неплохую модель сущностной модели с использованием EF, но только начальное прототипирование с доступом к данным через EF, а также бизнес-логику в методах Windows Forms.Теперь я хочу немного «формализовать» приложение и сосредоточиться на написании только логики пользовательского интерфейса в самих формах.

Мне интересно, какие структуры и шаблоны я могу использовать здесь?Я привык использовать свободный шаблон репозитория и настройку модели представления для своих проектов MVC3, но в течение нескольких лет я не проделал много работы над формами, и я не уверен.Некоторые недавние чтения показывают, что репозиторий должен выполнять CRUD, что делает его использование здесь излишним и избыточным.Я не хочу заходить так далеко, как полноценный дизайн MVVM или MVP, но я застрял, пытаясь понять, что и куда ставить.

Самая очевидная структура, которая возникает для меня, - это расширение моей модели сущностейвключить бизнес-логику и операции, например, добавить метод AllocatePayment в класс Client, чтобы распределить платеж, произведенный клиентом, по непогашенным ссудам для клиента и т. д., но это не пахнет совсем неправильно.Еще хуже то, что надвигающийся класс монолитного типа LoanManager со статическими методами для всего.

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

Ответы [ 5 ]

1 голос
/ 17 марта 2012

Базовая модель Windows Forms позволяет использовать Code-Behind, который связывает данные в элементы управления пользовательского интерфейса и из них.Следовательно, Code-Behind не является подходящим местом для размещения какой-либо тестируемой логики.

Когда я делал это (в том числе и с ASP.NET Web Forms), я всегда использовал Model-View-Presenter(MVP) .Эта модель проста и максимально разделяет проблемы.Если вы можете, то выберите WPF и перейдите с MVVM.Это пойдет дальше, позволив пользовательскому интерфейсу реагировать на повторное связывание.

Надеюсь, это поможет.

1 голос
/ 17 марта 2012

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

Это примерно то, где должна быть наша логика домена. Таким образом, модульное тестирование может быть написано легко. Вы создаете объект с некоторым состоянием. Вызовите метод и проверьте, оправданы ли ваши ожидания.

Постоянство может быть сделано либо с ActiveRecord / Repository / DataMapper Мне не очень нравится ActiveRecord, потому что он нарушает принцип единой ответственности, но это мое собственное предпочтение. Существует большое обсуждение каждого конкретного паттерна в EAA Фаулера, и здесь

Для упрощения я хотел бы использовать какое-то уже готовое решение, например CastleActiveRecord, или реализовать простой репозиторий. Существует отличный пост о том, как может выглядеть хранилище в наше время

Так что одним из вариантов является создание вашей модели, содержащей вашу бизнес-логику. Внедрить репозиторий / сущность, ответственную за постоянство. Формы должны представлять только вашу модель. А контроллеры могут обслуживать действия пользователя, поскольку их существует только конечный набор.

На всякий случай: привязка данных - это отличная особенность WinForms, которая позволяет связывать практически любые данные с элементами управления с нулевым кодом.

Если существуют какие-либо ограничения, которые должны соблюдаться вашей архитектурой, это может быть предметом обсуждения, поскольку у каждого есть свои плюсы и минусы. Но модель MVC здесь с нами с 80-х годов, и ее аудитория только расширяется:)

0 голосов
/ 12 марта 2012

Я не знаю WinForms (или EF или действительно .NET для всего, что имеет значение), но я сделаю предложение, основанное на том, что я знаю (или думаю, что могу вспомнить).

ПозволяетПредположим, у вас есть простой класс формы, который рассчитывает риск на основе 3 переменных: возраст, количество и цвет волос.При нажатии кнопки «Оценить» метка будет обновлена ​​соответствующей строкой: «Высокий», «Средний» и «Низкий».

public void btnRate_click() {
  var RiskScore = 0;
  if(txtAge.Value < 25)
    RiskScore += 2;
  else if(txtAge.Value < 40)
    RiskScore += 1;

  if(txtAmount.Value < 2.00)
    RiskScore += 10;
  else if(txtAmount.Value < 10.00)
    RiskScore += 3;
  else if(txtAmount.Value < 50.00)
    RiskScore += 5;

  if(txtHairColor.Value == "Red")
    RiskScore += 75;

  if(RiskScore < 2)
    lblResult.Value = "Low";
  else if(RiskScore < 8)
    lblResult.Value = "Medium";
  else
    lblResult.Value = "High";
}

Учитывая этот код, я бы начал с некоторого простого "«Prefactoring» перед постановкой некоторых тестов.Я склонен использовать стандартные рефакторинги, которые IDE, такие как ReSharper, могут выполнять автоматически, потому что они действительно безопасны и редко оставляют нежелательные побочные эффекты.

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

public void btnRate_click() {
  var RiskScore = 0;
  var Age = txtAge.Value;
  if(Age < 25)
    RiskScore += 2;
  else if(Age < 40)
    RiskScore += 1;

  var Amount = txtAmount.Value;
  if(Amount < 2.00)
    RiskScore += 10;
  else if(Amount < 10.00)
    RiskScore += 3;
  else if(Amount < 50.00)
    RiskScore += 5;

  var HairColor = txtHairColor.Value;
  if(HairColor == "Red")
    RiskScore += 75;

  var Result = ""
  if(RiskScore < 2)
    Result = "Low";
  else if(RiskScore < 8)
    Result = "Medium";
  else
    Result = "High";

  lblResult.Value = Result;
}

Этот рефакторинг был быстрым и может быть выполнен автоматически с помощью Ctrl+Alt+V Теперь потребуется немного времени, чтобы немного упорядочить код, переместивобъявления переменных вокруг.

public void btnRate_click() {
  var Age = txtAge.Value;
  var Amount = txtAmount.Value;
  var HairColor = txtHairColor.Value;

  var RiskScore = 0;
  var Result = ""

  if(Age < 25)
    RiskScore += 2;
  else if(Age < 40)
    RiskScore += 1;

  if(Amount < 2.00)
    RiskScore += 10;
  else if(Amount < 10.00)
    RiskScore += 3;
  else if(Amount < 50.00)
    RiskScore += 5;

  if(HairColor == "Red")
    RiskScore += 75;

  if(RiskScore < 2)
    Result = "Low";
  else if(RiskScore < 8)
    Result = "Medium";
  else
    Result = "High";

  lblResult.Value = Result;
}

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

Глядя на то, что делает этот блок кода,мы назовем этот новый класс RiskCalculator.После рефакторинга код должен выглядеть примерно так:

public void btnRate_click() {
  var Age = txtAge.Value;
  var Amount = txtAmount.Value;
  var HairColor = txtHairColor.Value;

  var Result = new RiskCalculator(Age, Amount, HairColor).Invoke();

  lblResult.Value = Result;
}

// In RiskCalculator.cs
public class RiskCalculator {
  private int Age;
  private double Amount;
  private string HairColor;

  public RiskCalculator(int Age, double Amount, string HairColor) {
    this.Age = Age;
    this.Amount = Amount;
    this.HairColor = HairColor;
  }

  public string Invoke() {
    var RiskScore = 0;
    var Result = ""

    if(Age < 25)
      RiskScore += 2;
    else if(Age < 40)
      RiskScore += 1;

    if(Amount < 2.00)
      RiskScore += 10;
    else if(Amount < 10.00)
      RiskScore += 3;
    else if(Amount < 50.00)
      RiskScore += 5;

    if(HairColor == "Red")
      RiskScore += 75;

    if(RiskScore < 2)
      Result = "Low";
    else if(RiskScore < 8)
      Result = "Medium";
    else
      Result = "High";
    return Result;
  }
}

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

В процессе написания тестов вы можете заметить, что RiskCalculator не совсем подходит для этого класса.Я имею в виду, если вы думаете об этом, данные, которые передаются в конструктор, фактически представляют ваш кредит!Учитывая, что у вас есть хорошая база тестов для текущего RiskCalculator, вы можете выполнить несколько рефакторингов Rename.Мы начнем с Метод переименования .

Метод Invoke действительно неинформативен как имя, поэтому мы переименуем его.Что это делает?Я бы сказал, что на самом деле он выполняет расчет риска, поэтому назовем его calculateRisk.После этого мы должны спросить себя, для чего он рассчитывает риск.Ответ - кредит, так что это будет наш второй рефакторинг.Мы будем использовать рефакторинг Rename Class, переименовывая RiskCalculator в Loan.Это становится нашим первым доменным объектом в системе.

// RiskCalculator.cs has now become Loan.cs
public class Loan {
  // ...

  public string CalculateRisk() {
    // ...
  }
}

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

Теперь, когда наш доменный объект очищен, мы можем вернуться к этому обработчику событий, который должен выглядеть примерно так:

public void btnRate_click() {
  var Age = txtAge.Value;
  var Amount = txtAmount.Value;
  var HairColor = txtHairColor.Value;

  var Result = new Loan(Age, Amount, HairColor).CalculateRisk();

  lblResult.Value = Result;
}

Вероятно, я бы исправил это, выполнив Встроенную временную переменную и рефакторинг Извлечение переменной:

public void btnRate_click() {
  var MicroLoan = new Loan(txtAge.Value, txtAmount.Value, txtHairColor.Value);
  lblResult.Value = MicroLoan.CalculateRisk();
}

Это довольно чисто сейчас, и кода на самом деле немноготам, чтобы испортить, так что нет особой необходимости в тестах на данный момент.

Надеюсь, это поможет.Я действительно не отвечал на ваши вопросы о репозиториях, но я надеюсь, что это даст вам возможность начать работу.Когда дело доходит до того, куда поместить вашу логику, помните о Принципе единой ответственности , который поможет вам решить, что входит в ваш репозиторий, а что нет.Это также может заставить вас разбить класс Loan на другие более мелкие, более сфокусированные классы.

Удачи!Brandon

0 голосов
/ 03 марта 2012

Чтобы упростить TDD, я предлагаю разделить всю бизнес-логику на отдельную библиотеку классов из кода форм Windows.Не пишите тесты для кода форм Windows;освободите проект Winforms от какой-либо бизнес-логики.Библиотека классов должна быть полностью покрыта тестами.

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

...