О чём Центр сообщений Xamarin.Forms мне не сообщил
Не так давно я сказал, что каждый раз, когда использую Центр Сообщений Xamarin.Forms — чувствую что жульничаю.
В конце концов, я же должен уметь структурировать приложение так, чтобы компоненты могли общаться друг с другом без посредника.
Но всегда есть особый случай…
Случай с Центром Сообщений
В основном проекте может произойти что-то, о чём он хочет чтобы знали проекты платформ… например, начало продолжительной фоновой загрузки.
И когда эта фоновая загрузка в проекте платформы завершается, ему нужно сообщить об этом кому-то (потенциально, большому количеству слушателей)
Также в одной вью-модели может произойти что-то, о чём нужно знать другой вью-модели…
Вот это и есть веские причины для использования Центра Сообщений.
Кажется, синтаксис очень простой…
MessagingCenter.Subscribe<TSender>(object, string, Action<TSender>);
и
MessagingCenter.Send<TSender>(TSender, string);
Кажется, что это жульничество и… неправильно.
На «Evolve 2016» в выступлении о производительности Xamarin.Forms, Джейсон Смит (Jason Smith) сказал, что вместо стандартного центра сообщений предпочитает использовать что-то другое, типа Prism.
Кто я, чтобы спорить с человеком, создавшим Xamarin.Forms? Но… Я бы предпочел пользоваться базовыми возможностями Xamarin.Forms, насколько это возможно.
О чём Центр Сообщений мне не сообщил
Огромная польза Центра Сообщений в том, что он делает возможным обмен между двумя классами, которые ничего друг о друге не знают. Фактически, не важно даже – существует ли экземпляр подписчика на момент отправки сообщения.
Другими словами — классы не связаны друг с другом.
Но главное, о чём не сообщил мне Центр Сообщений, и причина по которой я никогда не использовал его правильно — то как я использовал аргумент TSender
в функции MessagingCenter.Send
.
Я всегда ставил туда имя класса-отправителя.
Например, если сообщение отправляется из LoginViewModel
, а подписываются на него в UserDetailViewModel
— подписка выглядела бы так:
MessagingCenter.Subscribe<LoginViewModel>(this, "successful_login", (lvm) => HandleLogin(lvm) );
Получается что UserDetailViewModel
знает о LoginViewModel
… и это не выглядит правильно.
Так и есть.
Не буду вдаваться в причины того, с чего я решил, что это правильный способ подписки…
Верным решением будет создать класс, не имеющий ничего общего ни с отправителем, ни с подписчиком.
Это может быть класс-маркер или класс без свойств и функций.
Наличие такого класса, который относится только к сообщению и не связан с отправителем и получателем, уже смотрится правильно и не так читерно.
Отправка станет выглядеть так:
MessagingCenter.Send<LoginMessage>(new LoginMessage, "successful_login");
А подписка так:
MessagingCenter.Subscribe<LoginMessage>(this, "successful_login", (lm) => HandleLogin(lm));
Во-от… намного лучше — теперь вью-модели ничего не знают друг о друге.
И даже лучше — теперь сообщение может исходить от одного из проектов платформы без всяких проблем (Нет никакой возможности реализовать отправку класса из проекта платформы в основной проект).
В чём мораль сей басни?
В Центре Сообщений всегда используйте общий или маркерный класс, который не связан ни с отправителем, ни с получателем!
Дополнение от переводчика
Для упрощения подписки на сообщения я создаю классы со статическими текстовыми свойствами, значение которых совпадает с их именем. Это позволяет не запоминать текстовые «имена» сообщений и передавать полезные данные через объект сообщения, если необходимо:
public class LoginMessage
{
public static string SuccessfulLogin => nameof(SuccessfulLogin);
public static string InvalidUserName => nameof(InvalidUserName);
public static string SessionExpired => nameof(SessionExpired);
public LoginMessageData Data { get; }
public LoginMessage(LoginMessageData data)
{
Data = data;
}
}
тогда подписка станет выглядеть вот так:
MessagingCenter.Subscribe<LoginMessage>(this, LoginMessage.InvalidUserName, (lm) => HandleLogin(lm));
а отправка так:
MessagingCenter.Send<LoginMessage>(new LoginMessage(new LoginMessageData()), LoginMessage.InvalidUserName);