Уменьшаем хаос в UI: Паттерн MVP

Разделяем код и властвуем в разработке интерфейсов!

Так все и работает
Так все и работает

Общая информация

MVP (Model, View, Presenter)- это шаблон проектирования графических интерфейсов из семейства MV* (к ним также относятся MVC и MVVM).

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

Я думаю, если вы как-то сопричастны к созданию интерфейсов и программированию их логики, то скорее всего что-то да и слышали о них.

Мотивация

Важным вопросом который нужно задать, когда начинаешь использовать тот или иной шаблон проектирования, является – "Зачем? Что это даст?"

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

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

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

Я решил написать статью, чтобы максимально наглядно и доходчиво рассказать об этом (с примерами кода, с диаграммами) . Буду рад, если кто-то заинтересуется и попробует его использовать в своих проектах.

Подготовка

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

Так, например, мы видим, что ProfilePresenter является связующим звеном между ProfileModel и ProfileView.

Диаграмма классов профиля игрока.
Диаграмма классов профиля игрока.

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

Model

Концепция Model’и заключается в игровых данных, а если говорить точнее — хранение, предоставление и изменение (это геттеры, сеттеры и ивенты/события).

Если перефразировать, модель отвечает за состояние игры в данный момент времени и предоставляет актуальную информацию.

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

using System; namespace UI.Profile { public class ProfileModel { public event Action PlayerNameChanged; public event Action PlayerLevelChanged; private string _playerName; private int _playerLevel; public ProfileModel() { _playerName = "Linar Khilazhev"; _playerLevel = 23; } public void SetPlayerName(string name) { _playerName = name; PlayerNameChanged?.Invoke(); } public string GetPlayerName() => _playerName; public void SetPlayerLevel(int level) { _playerLevel = level; PlayerLevelChanged?.Invoke(); } public int GetPlayerLevel() => _playerLevel; } }

В нашей модели хранится два значения и для каждой из них есть GET/SET методы, также событие на их изменение.

При желание, в каждое из событий можно прокидывать обновленное значение:

{ public event Action<int>PlayerLevelChanged; public void SetPlayerLevel(int level) { _playerLevel = level; PlayerLevelChanged?.Invoke(_playerLevel); } }

View

Концепция View’шки — заключается в отображение информации/данных, это непосредственно информация про внутреннее содержание интерфейса (ссылки на текстовые/графические элементы и систем ввода/вывода)

Кроме ссылок, View также может иметь события на различные пользователя действия, такие как: нажатие кнопки, перелистывание страницы, ввод значений в текстовые поля

using TMPro; using UnityEngine; namespace UI.Profile { public interface IView { public void Enable(); public void Disable(); } public interface IProfileView : IView { public void SetPlayerName(string name); public void SetPlayerLevel(int level); } public class ProfileView : MonoBehaviour, IProfileView { public TextMeshProUGUI _playerNameLabel; public TextMeshProUGUI _playerLevelLabel; public void Enable() { gameObject.SetActive(true); } public void Disable() { gameObject.SetActive(false); } public void SetPlayerName(string name) { _playerNameLabel.SetText(name); } public void SetPlayerLevel(int level) { _playerLevelLabel.SetText(level.ToString()); } } }

В примере кода интерфейсы и сам класс объединил для наглядности (в реальности они лежат раздельно)

Presenter

Presenter является связующим звеном между данными (Model) и отображением (View).

Его главной задачей является инициализация начальных значений, подпись на изменения данных и обновления в интерфейсах.

using Organization; namespace UI.Profile { public class ProfilePresenter { private ProfileModel _profileModel; private IProfileView _profileView; public ProfilePresenter() { _profileModel = ProfileManager.GetProfileModel(); _profileView = ProfileManager.GetProfileView(); _profileModel.PlayerLevelChanged += UpdatePlayerLevel; _profileModel.PlayerNameChanged += UpdatePlayerName; _profileView.SetPlayerName(_profileModel.GetPlayerName()); _profileView.SetPlayerLevel(_profileModel.GetPlayerLevel()); } private void UpdatePlayerName() { _profileView.SetPlayerName(_profileModel.GetPlayerName()); } private void UpdatePlayerLevel() { _profileView.SetPlayerLevel(_profileModel.GetPlayerLevel()); } ~ProfilePresenter() { _profileModel.PlayerLevelChanged -= UpdatePlayerLevel; _profileModel.PlayerNameChanged -= UpdatePlayerName; } } }

Back to Unity

С устройством и логикой MVP разобрались, а как это будет выглядеть на движке Unity?

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

Сущности которые находятся на сцене
Сущности которые находятся на сцене

Изначально, на сцене нет никаких интерфейсов и логики, они создаются через класс Starter. cs (который прикреплен к одноименному объекту на сцене)

using UnityEngine; namespace Organization { public class Starter : MonoBehaviour { private void Awake() { CreateProfilePresenter(); } private void CreateProfilePresenter() { var profilePresenter = ProfileManager.GetProfilePresenter(); } } }

Этот скрипт обращается к ProfileManager. cs и легким движением руки создает ProfilePresenter (который внутри конструктора создает ProfileModel и ProfileView)

Ниже сам код, создание/получение нужных ссылок.

using UI.Profile; using UnityEngine; namespace Organization { public class ProfileManager { private static IProfileView _profileView; private static ProfileModel _profileModel; private static ProfilePresenter _profilePresenter; public static IProfileView GetProfileView() { if (_profileView == null) { var prefab = Resources.Load<ProfileView>(nameof(ProfileView)); _profileView = Object.Instantiate<ProfileView>(prefab, Vector3.zero, Quaternion.identity); } return _profileView; } public static ProfileModel GetProfileModel() { if (_profileModel == null) { _profileModel = new ProfileModel(); } return _profileModel; } public static ProfilePresenter GetProfilePresenter() { if (_profilePresenter == null) { _profilePresenter = new ProfilePresenter(); } return _profilePresenter; } } }

Теперь посмотрим, как выглядит ProfileView, который мы достаем из папки Resources.

Иерархия префаба
Иерархия префаба

Что в итоге?

При запуске игры, у нас на сцене создается ProfileView и отображается на сцену наши данные из ProfileModel.

отображение ProfileView
отображение ProfileView

Также оставлю ссылку на репозиторий с кодом

Надеюсь, эта статья оказалась интересной и полезной.

Буду рад замечаниям, обратной связи, лайкам, репостам, скачиваниям.

Спасибо за внимание.

2727
30 комментариев

гайды по мвп в 2023 это кек конечно, может геймдев в какой то альтернативной временной ветке развивается...

7
Ответить

Критикуешь - предлагай

1
Ответить

Просто "геймдеверы" зачастую стартуют с визуального программирования. UE по сути под него заточен, вон в юнити что-то добавили... Им не до базы

Ответить

Не знаю, что в Unity сейчас актуально, но MVP это говно динозавров. Однажды пришлось поработать с этим в мобильной разработке и больше не хочется к такому возвращаться. Могу ошибаться, но думаю, что в Unity можно использовать тот же MVI или что-то вроде MVIVM (mvi + ViewModel). Хотя, скорее всего в Unity можно использовать MVVM, один из самых годных архитектурных паттернов.

4
Ответить

Хорошая статья, доходчиво написано. Несколько вопросов осталось. Здесь не использован интерфейс для модели в цели упрощения примера или есть другая причина? В примере был использован менеджер вместо DI для создания объектов, это тоже для простоты или это и в практике используется?

2
Ответить

Не совсем понял про "интерфейс модели", распишите чуть подробнее


А менеджер вместо нормального DI был использован для простоты. В практике такое редко встретишь)

1
Ответить

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

2
Ответить