Взаимодействие между слабо связанными компонентами

Примечание.

Эта электронная книга была опубликована весной 2017 года и с тех пор не была обновлена. Есть много в книге, которая остается ценным, но некоторые из материалов устарели.

Шаблон "публикация-подписка" — это шаблон обмена сообщениями, в котором издатели отправляют сообщение без знания о получателях, известных как подписчики. Аналогичным образом подписчики прослушивают определенные сообщения, не зная издателей.

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

Общие сведения о MessagingCenter

Класс Xamarin.FormsMessagingCenter реализует шаблон публикации и подписки, позволяя обмен данными между компонентами, которые неудобны для связи по ссылкам на объекты и типы. Этот механизм позволяет издателям и подписчикам взаимодействовать без ссылки друг на друга, что помогает сократить зависимости между компонентами, а также позволяет компонентам независимо разрабатывать и тестировать их.

Класс MessagingCenter предоставляет функцию многоадресной публикации и подписки. Это означает, что может быть несколько издателей, публикующих одно сообщение, и может быть несколько подписчиков, прослушивающих одно и то же сообщение. Рис. 4-1 иллюстрирует эту связь:

Multicast publish-subscribe functionality

Рис. 4-1. Функция многоадресной публикации и подписки

Издатели отправляют сообщения с помощью метода MessagingCenter.Send, а подписчики прослушивают сообщения с помощью метода MessagingCenter.Subscribe. Кроме того, подписчики могут также отменять подписку на сообщения, если это необходимо, с помощью метода MessagingCenter.Unsubscribe.

На внутреннем уровне класс MessagingCenter использует слабые ссылки. Это означает, что он не будет поддерживать объекты в активном состоянии и позволит им собирать мусор. Поэтому необходимо отменять подписку на сообщения только в том случае, если классу больше не требуется получать сообщения.

Мобильное приложение eShopOnContainers использует MessagingCenter класс для обмена данными между слабо связанных компонентов. Приложение определяет три сообщения:

  • Сообщение AddProduct публикуется классом CatalogViewModel при добавлении элемента в корзину покупок. В свою очередь, BasketViewModel класс подписывается на сообщение и увеличивает количество элементов в корзине покупок в ответе. Кроме того, BasketViewModel класс также отменяет подписку из этого сообщения.
  • Сообщение Filter публикуется классом CatalogViewModel , когда пользователь применяет фильтр фирменной символики или типа к элементам, отображаемым из каталога. В свою очередь, CatalogView класс подписывается на сообщение и обновляет пользовательский интерфейс таким образом, чтобы отображались только элементы, соответствующие критериям фильтра.
  • Сообщение ChangeTab публикуется классом MainViewModel при CheckoutViewModel переходе к MainViewModel следующему успешному созданию и отправке нового заказа. В свою очередь MainView , класс подписывается на сообщение и обновляет пользовательский интерфейс, чтобы вкладка "Мой профиль " активна для отображения заказов пользователя.

Примечание.

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

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

Совет

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

Дополнительные сведения см. в MessagingCenterразделе MessagingCenter.

Определение сообщения

MessagingCenter сообщения — это строки, используемые для идентификации сообщений. В следующем примере кода показаны сообщения, определенные в мобильном приложении eShopOnContainers:

public class MessageKeys  
{  
    // Add product to basket  
    public const string AddProduct = "AddProduct";  

    // Filter  
    public const string Filter = "Filter";  

    // Change selected Tab programmatically  
    public const string ChangeTab = "ChangeTab";  
}

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

Публикация сообщения

Издатели уведомляют подписчиков о сообщении с помощью одной из перегрузок MessagingCenter.Send. В следующем примере кода демонстрируется публикация AddProduct сообщения:

MessagingCenter.Send(this, MessageKeys.AddProduct, catalogItem);

В этом примере Send метод задает три аргумента:

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

Метод Send будет публиковать сообщение и его полезные данные, используя подход fire-and-forget. Поэтому сообщение отправляется, даже если отсутствуют подписчики, зарегистрированные для получения сообщения. В этом случае отправленное сообщение игнорируется.

Примечание.

Метод MessagingCenter.Send может использовать универсальные параметры для управления доставкой сообщений. Таким образом, несколько сообщений, которые совместно используют удостоверение сообщения, но отправляют различные типы данных полезных данных, могут быть получены различными подписчиками.

Подписка на сообщение

Подписчики могут зарегистрироваться для получения сообщения с помощью одной из перегрузок MessagingCenter.Subscribe. В следующем примере кода показано, как мобильное приложение eShopOnContainers подписывается на и обрабатывает AddProduct сообщение:

MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(  
    this, MessageKeys.AddProduct, async (sender, arg) =>  
{  
    BadgeCount++;  

    await AddCatalogItemAsync(arg);  
});

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

Совет

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

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

Отмена подписки из сообщения

Если подписчики больше не должны получать сообщения, можно отменить подписку на них. Это достигается с одной из MessagingCenter.Unsubscribe перегрузок, как показано в следующем примере кода:

MessagingCenter.Unsubscribe<CatalogViewModel, CatalogItem>(this, MessageKeys.AddProduct);

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

Итоги

Класс Xamarin.FormsMessagingCenter реализует шаблон публикации и подписки, позволяя обмен данными между компонентами, которые неудобны для связи по ссылкам на объекты и типы. Этот механизм позволяет издателям и подписчикам взаимодействовать без ссылки друг на друга, что помогает сократить зависимости между компонентами, а также позволяет компонентам независимо разрабатывать и тестировать их.