在鬆散藕合的元件之間通訊

提示

本內容節錄自《Enterprise Application Patterns Using .NET MAUI》電子書,可以從 .NET Docs 取得,也可以免費下載 PDF 離線閱讀。

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

發行-訂閱模式是一種訊息模式,發行者可以在不知道任何接收者 (稱為訂閱者) 的情況下傳送訊息。 同樣地,訂閱者可以在不知道任何發行者的情況下接聽特定訊息。

.NET 中的事件會實作發行-訂閱模式,而且在不需要鬆散藕合的情況下,是元件之間最簡單的通訊層傳訊方法,例如控制項和包含該控制項的頁面。 不過,發行者和訂閱者存留期會藉由物件參考彼此藕合,而訂閱者類型必須具有發行者類型的參考。 這會造成記憶體管理問題,特別是在有短期的物件訂閱靜態或長期物件的事件時。 如果未移除事件處理常式,訂閱者將會由發行者中的參考保持運作,這樣可防止或延遲訂閱者的記憶體回收。

MVVM Toolkit Messenger 簡介

MVVM Toolkit IMessenger 介面說明發行-訂閱模式,允許在不方便透過物件和類型參考連結的元件之間進行以訊息為基礎的通訊。 此機制可讓發行者和訂閱者在彼此沒有直接參考的情況下進行通訊,有助於減少元件之間的相依性,同時還能讓元件獨立開發和測試。

注意

MVVM Toolkit Messenger 是 CommunityToolkit.Mvvm 套件的一部分。 如需如何將套件新增至專案的相關資訊,請參閱 Microsoft 開發人員中心的 MVVM Toolkit 簡介

警告

.NET MAUI 含有內建的 MessagingCenter 類別,此類別已不建議使用且應轉換為 MVVM Toolkit Messenger。

IMessenger 介面允許多點傳送發行-訂閱功能。 這表示可以有多個發行單一訊息的發行者,而且可以有多個接聽相同訊息的訂閱者。 下圖說明此關係:

Multicast publish-subscribe functionality.

CommunityToolkit.Mvvm 套件隨附的 IMessenger 介面有兩種實作方式。 WeakReferenceMessenger 使用弱式參考,這會造成訊息訂閱者更容易清除。 如果您的訂閱者沒有清楚定義生命週期,則這是不錯的選擇。 StrongReferenceMessenger 使用強式參考,這會造成效能提升,而且可以更清楚地控制訂閱的存留期。 例如,如果工作流程的存留期受到嚴格控制 (例如,訂閱繫結至頁面的 OnAppearingOnDisappearing 方法),則在重視效能的情況下,StrongReferenceManager 可能是較佳的選擇。 不論是參考 WeakReferenceMessenger.Default 還是 StrongReferenceMessenger.Default,這兩種實作方式都可以與準備要使用的預設實作一起使用。

注意

雖然 IMessenger 介面允許鬆散藕合類別之間進行通訊,但不會就這個問題提供僅限架構的解決方案。 例如,檢視模型與檢視之間的通訊也可以藉由繫結引擎和透過屬性變更通知來實現。 此外,也可以在導覽期間傳遞資料,以實現兩個檢視模型之間的通訊。

eShopOnContainers 多平台應用程式會使用 WeakReferenceMessenger 類別在鬆散藕合的元件之間進行通訊。 應用程式會定義名為 AddProductMessage 的單一訊息。 在購物籃中新增項目時,CatalogViewModel 類別就會發佈 AddProductMessage。 作為回報,類別 CatalogView 就會訂閱該訊息,並在回應時使用此訊息搭配動畫來強調顯示新增的商品品項。

在 eShopOnContainers 多平台應用程式中,使用 WeakReferenceMessenger 來更新 UI 以回應在另一個類別發生的動作。 因此,訊息會從類別執行所在的執行緒發佈,且訂閱者會在相同的執行緒上接收訊息。

提示

執行 UI 更新時封送至 UI 或主執行緒。 如果未在此執行緒上更新使用者介面,則會造成應用程式損毀或變得不穩定。

如果需要從背景執行緒傳送的訊息來更新 UI,請叫用 MainThread.BeginInvokeOnMainThread 方法,在訂閱者中的 UI 執行緒上處理訊息。

如需 Messenger 的相關資訊,請參閱 Microsoft 開發人員中心的 Messenger

定義訊息

IMessenger 訊息是提供自訂承載的自訂物件。 下列程式碼範例顯示在 eShopOnContainers 多平台應用程式中定義的 AddProductMessage 訊息:

public class AddProductMessage : ValueChangedMessage<int>
{
    public AddProductMessage(int count) : base(count)
    {
    }
}

基底類別是使用 ValueChangedMessage<T> 定義,其中 T 可以是傳遞資料所需的任何類型。 訊息發行者和訂閱者都可以預期特定類型的訊息 (例如,AddProductMessage)。 這可協助確保雙方均同意傳訊合約,以及隨該合約提供的資料一致。 此外,此方法可提供編譯時間型別安全和重構支援。

發佈訊息

若要發佈訊息,我們必須使用 IMessenger.Send 方法。 最常見的存取方法是透過 WeakReferenceMessenger.Default.SendStrongReferenceMessenger.Default.Send。 傳送的訊息可以是任何物件類型。 下列程式碼範例示範如何發佈 AddProduct 訊息:

WeakReferenceMessenger.Default.Send(new Messages.AddProductMessage(BadgeCount));

在此範例中,Send 方法會指定下游訂閱者要接收的 AddProductMessage 物件新執行個體。 當多個不同訂閱者需要接收相同類型的訊息而不收到錯誤訊息時,可以新增額外的第二個權杖參數來使用。

Send 方法將使用射後不理方法來發佈訊息和任何承載資料。 因此,即使沒有任何訂閱者已註冊要接收訊息,也會傳送訊息。 在此情況下,會忽略已傳送的訊息。

訂閱訊息

訂閱者可以註冊,以使用其中一個 IMessenger.Register<T> 多載來接收訊息。 下列程式碼範例示範 eShopOnContainers 多平台應用程式如何訂閱和處理 AddProductMessage 訊息:

WeakReferenceMessenger.Default
    .Register<CatalogView, Messages.AddProductMessage>(
        this,
        async (recipient, message) =>
        {
            await recipient.Dispatcher.DispatchAsync(
                async () =>
                {
                    await recipient.badge.ScaleTo(1.2);
                    await recipient.badge.ScaleTo(1.0);
                });
        });

在前述範例中,Register 方法會訂閱 AddProductMessage 訊息並在回應接收訊息時執行回撥委派。 這個回撥委派是以 Lambda 運算式指定,可執行更新 UI 的程式碼。

注意

請勿在回撥委派中使用 this,以避免擷取委派內的該物件。 這樣有助於提升效能。 因此,請改用 recipient 參數。

如果提供承載資料,請勿嘗試從回撥委派中修改承載資料,因為數個執行緒可以同時存取接收的資料。 在此案例中,承載資料應該不可變,以避免發生並行錯誤。

取消訂閱訊息

若訂閱者不想再接收訊息,則應從訊息中取消訂閱。 這可透過其中一個 IMessenger.Unregister 多載來達成,如下列程式碼範例所示:

WeakReferenceMessenger.Default.Unregister<Messages.AddProductMessage>(this);

注意

在此範例中,完全不需要呼叫 Unregister,因為 WeakReferenceMessenger 會允許對未使用的物件回收記憶體。 如果使用 StrongReferenceMessenger,則建議對任何不再使用的訂閱呼叫 Unregister

在此範例中,Unsubscribe 方法語法會指定訊息的型別引數,以及接聽訊息的收件者物件。

摘要

MVVM Toolkit IMessenger 介面說明發行-訂閱模式,允許在不方便透過物件和類型參考連結的元件之間進行以訊息為基礎的通訊。 此機制可讓發行者和訂閱者在彼此沒有參考的情況下進行通訊,有助於減少元件之間的相依性,同時還能讓元件獨立開發和測試。