Comunicação entre componentes pouco acoplados
Dica
Esse conteúdo é um trecho do livro eletrônico, Padrões de Aplicativo Empresarial Usando .NETMAUI, disponível em .NET Docs ou em PDF para download gratuito que pode ser lido off-line.
O padrão de publicação-assinatura é um padrão de troca de mensagens em que os publicadores enviam mensagens sem conhecer nenhum receptor, conhecido como assinante. Da mesma forma, os assinantes escutam mensagens específicas, sem conhecer nenhum publicador.
Os eventos no .NET implementam o padrão de publicação-assinatura, e são a abordagem mais simples para uma camada de comunicação entre os componentes se o acoplamento solto não é necessário, como um controle e a página que o contém. No entanto, os tempos de vida do publicador e do assinante são acoplados por referências de objeto entre si e o tipo de assinante deve ter uma referência para o tipo de publicador. Isso pode criar problemas de gerenciamento de memória, especialmente quando há objetos de curta duração que assinam o evento de um objeto estático ou de longa duração. Se o manipulador de eventos não for removido, o assinante será mantido ativo pela referência a ele no publicador e isso impedirá ou atrasará a coleta de lixo do assinante.
Introdução ao MVVM Toolkit Messenger
A interface do MVVM Toolkit IMessenger
descreve o padrão de publicação-assinatura, permitindo a comunicação baseada em mensagens enre componentes que são inconvenientes para vincular por referências de objeto e tipo. Esse mecanismo permite que publicadores e assinantes se comuniquem sem uma referência direta entre eles, ajudando a reduzir as dependências entre os componentes, permitindo também que os componentes sejam desenvolvidos e testados independentemente.
Observação
O MVVM Toolkit Messenger faz parte do pacote CommunityToolkit.Mvvm
. Para obter informações sobre como adicionar o pacote ao seu projeto, confira Introdução ao MVVM Toolkit na Central de Desenvolvedores da Microsoft.
Aviso
O .NET MAUI contém uma classe interna MessagingCenter
que não é mais recomendada para uso. Em vez disso, use o MVVM Toolkit Messenger.
A interface IMessenger
permite a funcionalidade multicast de publicação/assinatura. Isso significa que pode haver vários publicadores que publicam uma única mensagem e pode haver vários assinantes ouvindo a mesma mensagem. A imagem abaixo ilustrará essa relação:
Há duas implementações da interface IMessenger
que vêm com o pacote CommunityToolkit.Mvvm
. O WeakReferenceMessenger
usa referências fracas que podem resultar em uma limpeza mais fácil para assinantes de mensagens. Essa será uma boa opção se os assinantes não tiverem um ciclo de vida claramente definido. O StrongReferenceMessenger
usa referências fortes que podem resultar em melhor desempenho e um tempo de vida mais claramente controlado da assinatura. Se você tiver um fluxo de trabalho com um tempo de vida muito controlado (por exemplo, uma assinatura associada aos métodos OnAppearing
e OnDisappearing
de uma página), StrongReferenceManager
poderá ser uma opção melhor se o desempenho for uma preocupação. Ambas as implementações estão disponíveis com implementações padrão prontas para uso referenciando WeakReferenceMessenger.Default
ou StrongReferenceMessenger.Default
.
Observação
Embora a interface IMessenger
permita a comunicação entre classes pouco acopladas, ela não é a única solução arquitetônica para esse problema. Por exemplo, a comunicação entre um modelo de exibição e uma exibição também pode ser obtida pelo mecanismo de associação e por meio de notificações de alteração de propriedade. Além disso, a comunicação entre dois modelos de exibição também pode ser obtida passando dados durante a navegação.
O aplicativo multiplataforma eShop usa a classe WeakReferenceMessenger
para se comunicar entre os componentes pouco acoplados. O aplicativo define uma única mensagem chamada AddProductMessage
. A mensagem AddProductMessage
é publicada pela classe CatalogViewModel
quando um item é adicionado à cesta de compras. Em troca, a classe CatalogView
assina a mensagem e usa isso para destacar a adição do produto com uma animação em resposta.
No aplicativo multiplataforma eShop, WeakReferenceMessenger
é usado para atualizar a interface do usuário em resposta a uma ação que ocorre em outra classe. Portanto, as mensagens são publicadas do thread no qual a classe está sendo executada, com assinantes recebendo a mensagem no mesmo thread.
Dica
Realize o marshaling na interface do usuário ou no thread principal ao fazer atualizações na interface do usuário. Se as atualizações nas interfaces do usuário não forem feitas nesse thread, isso poderá fazer com que o aplicativo falhe ou fique instável.
Se uma mensagem enviada de um thread em segundo plano for necessária para atualizar a interface do usuário, processe a mensagem no thread da interface do usuário no assinante chamando o método MainThread.BeginInvokeOnMainThread
.
Para obter mais informações sobre Messenger
, confira Messenger no Centro de Desenvolvedores da Microsoft.
Definindo uma mensagem
As mensagens do IMessenger
são objetos personalizados que fornecem conteúdo personalizado. O exemplo de código a seguir mostra a mensagem AddProductMessage
definida no aplicativo multiplataforma eShop:
public class AddProductMessage : ValueChangedMessage<int>
{
public AddProductMessage(int count) : base(count)
{
}
}
A classe base é definida usando ValueChangedMessage<T>
, em que T
pode ser de qualquer tipo necessário para passar dados. Editores e assinantes de mensagens podem esperar mensagens de um tipo específico (por exemplo, AddProductMessage
). Isso pode ajudar a garantir que ambas as partes tenham concordado com um contrato de mensagens e que os dados fornecidos com esse contrato sejam consistentes. Além disso, essa abordagem fornece suporte para a segurança do tipo em tempo de compilação e refatoração.
Publicando uma mensagem
Para publicar uma mensagem, precisaremos usar o método IMessenger.Send
. Ele pode ser acessado com mais frequência por meio de WeakReferenceMessenger.Default.Send
ou StrongReferenceMessenger.Default.Send
. A mensagem enviada pode ser de qualquer tipo de objeto. O exemplo de código a seguir demonstra a publicação da mensagem AddProduct
:
WeakReferenceMessenger.Default.Send(new Messages.AddProductMessage(BadgeCount));
Neste exemplo, o método Send
especifica que fornece uma nova instância do objeto AddProductMessage
para os assinantes downstream receberem. Um segundo parâmetro de token adicional pode ser adicionado para uso quando vários assinantes diferentes precisam receber mensagens do mesmo tipo sem receber a mensagem errada.
O método Send
publicará a mensagem, bem como os dados de conteúdo, usando uma abordagem do tipo “disparar e esquecer”. Portanto, a mensagem é enviada mesmo quando não há assinantes registrados para recebê-la. Nessa situação, a mensagem enviada é ignorada.
Assinando uma mensagem
Os assinantes podem se registrar para receber uma mensagem usando uma das sobrecargas IMessenger.Register<T>
. O exemplo de código a seguir demonstra como o aplicativo multiplataforma eShop assina e processa a mensagem 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);
});
});
No exemplo anterior, o método Register
assina a mensagem AddProductMessage
e executa um representante de retorno de chamada em resposta ao recebimento da mensagem. Esse representante de retorno de chamada, especificado como uma expressão lambda, executa o código que atualiza a interface do usuário.
Observação
Evite o uso de this
dentro do delegado de retorno de chamada para evitar capturar esse objeto dentro do delegado. Isso pode ajudar a aprimorar o desempenho. Em vez disso, use o parâmetro recipient
.
Se os dados de conteúdo forem fornecidos, não tente modificá-los de dentro de um representante de retorno de chamada porque vários threads podem estar acessando os dados recebidos simultaneamente. Nesse cenário, os dados de conteúdo devem ser imutáveis para evitar erros de simultaneidade.
Cancelando a assinatura de uma mensagem
Os assinantes podem cancelar a assinatura de mensagens que não desejam mais receber. Isso é feito com uma das sobrecargas IMessenger.Unregister
, conforme demonstrado no seguinte exemplo de código:
WeakReferenceMessenger.Default.Unregister<Messages.AddProductMessage>(this);
Observação
Neste exemplo, não é totalmente necessário chamar Unregister
, pois WeakReferenceMessenger
permitirá que objetos não utilizados sejam coletados como lixo. Se o StrongReferenceMessenger
tiver sido usado, será aconselhável chamar Unregister
todas as assinaturas que não estão mais em uso.
Neste exemplo, a sintaxe do método Unsubscribe
especifica o argumento de tipo da mensagem e o objeto de destinatário que está escutando mensagens.
Resumo
A interface do MVVM Toolkit IMessenger
descreve o padrão de publicação-assinatura, permitindo a comunicação baseada em mensagens enre componentes que são inconvenientes para vincular por referências de objeto e tipo. Esse mecanismo permite que publicadores e assinantes se comuniquem sem uma referência entre eles, ajudando a reduzir as dependências entre os componentes, permitindo também que os componentes sejam desenvolvidos e testados independentemente.