Вы узнаете:
public partial class Form1 : Form
{
// Логика работы с базой данных
// Обработка нажатий кнопок
// Валидация данных
// Работа с API
// Графика и анимация
// ВСЕ В ОДНОМ МЕСТЕ!
private void button1_Click(object sender, EventArgs e)
{
// Сотни строк кода...
}
}
Архитектурный паттерн — это проверенное решение для организации кода в приложении.
| Компонент | Обязанности |
|---|---|
| Model | Данные, бизнес-логика, база данных |
| View | Отображение интерфейса, передача действий |
| Presenter | Посредник: получает данные → обновляет View |
public interface ICalculatorView
{
string FirstNumber { get; set; }
string SecondNumber { get; set; }
string Result { get; set; }
event EventHandler AddClicked;
event EventHandler SubtractClicked;
void ShowError(string message);
}
public class CalculatorModel
{
public int Add(int a, int b) => a + b;
public int Subtract(int a, int b) => a - b;
}
public class CalculatorPresenter
{
private readonly ICalculatorView _view;
private readonly CalculatorModel _model;
public CalculatorPresenter(ICalculatorView view, CalculatorModel model)
{
_view = view;
_model = model;
_view.AddClicked += OnAddClicked;
_view.SubtractClicked += OnSubtractClicked;
}
private void OnAddClicked(object sender, EventArgs e)
{
try
{
int a = int.Parse(_view.FirstNumber);
int b = int.Parse(_view.SecondNumber);
int result = _model.Add(a, b);
_view.Result = result.ToString();
}
catch (Exception ex)
{
_view.ShowError("Ошибка: " + ex.Message);
}
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
<TextBox Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}" />
<Label Content="{Binding UserName}" />
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func _canExecute;
public RelayCommand(Action execute, Func canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute();
public void Execute(object parameter) => _execute();
}
public class MainViewModel : ViewModelBase
{
private string _userName;
private List<Person> _people;
public MainViewModel()
{
LoadPeopleCommand = new RelayCommand(LoadPeople);
SaveCommand = new RelayCommand(Save, CanSave);
}
public string UserName
{
get => _userName;
set
{
_userName = value;
OnPropertyChanged();
}
}
public List<Person> People
{
get => _people;
set { _people = value; OnPropertyChanged(); }
}
public ICommand LoadPeopleCommand { get; }
public ICommand SaveCommand { get; }
private void LoadPeople()
{
People = Database.GetAllPersons();
}
private void Save()
{
Database.SavePerson(SelectedPerson);
}
}
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<!-- Ввод имени -->
<TextBox Text="{Binding UserName}" />
<!-- Список людей -->
<DataGrid ItemsSource="{Binding People}"
SelectedItem="{Binding SelectedPerson}" />
<!-- Кнопки -->
<Button Content="Загрузить" Command="{Binding LoadPeopleCommand}" />
<Button Content="Сохранить" Command="{Binding SaveCommand}" />
</Grid>
</Window>
| Характеристика | MVP | MVVM |
|---|---|---|
| Сложность | Средняя | Высокая |
| Связь View-Presenter | View знает о Presenter | View не знает о ViewModel |
| Биндинг | Ручной | Автоматический |
| Платформы | WinForms, Android | WPF, Xamarin, MAUI |
| Тестирование | Легко | Легко |
| Код в View | Есть (события) | Почти отсутствует |
| Порог входа | Низкий | Высокий |
[TestClass]
public class CalculatorPresenterTests
{
[TestMethod]
public void Add_ValidNumbers_ShowsSum()
{
// Arrange
var mockView = new Mock<ICalculatorView>();
mockView.Setup(v => v.FirstNumber).Returns("5");
mockView.Setup(v => v.SecondNumber).Returns("3");
var presenter = new CalculatorPresenter(mockView.Object, new CalculatorModel());
// Act
mockView.Raise(v => v.AddClicked += null, EventArgs.Empty);
// Assert
mockView.VerifySet(v => v.Result = "8", Times.Once);
}
[TestMethod]
public void Add_InvalidNumbers_ShowsError()
{
var mockView = new Mock<ICalculatorView>();
mockView.Setup(v => v.FirstNumber).Returns("abc");
var presenter = new CalculatorPresenter(mockView.Object, new CalculatorModel());
mockView.Raise(v => v.AddClicked += null, EventArgs.Empty);
mockView.Verify(v => v.ShowError(It.IsAny<string>()), Times.Once);
}
}
public class MainPresenter
{
private IDataBase _db = new SqlDatabase(); // Жесткая связь!
}public class MainPresenter
{
private readonly IDataBase _db;
private readonly ILogger _logger;
public MainPresenter(IDataBase db, ILogger logger)
{
_db = db;
_logger = logger;
}
}
// Настройка DI-контейнера
var services = new ServiceCollection();
services.AddSingleton<IDataBase, SqlDatabase>();
services.AddSingleton<ILogger, FileLogger>();
services.AddTransient<MainPresenter>();🙏 Спасибо за внимание!
Вопросы?