![](/img/trans.png)
[英]Design pattern for handling multiple message types with callback or event raising
[英]Design pattern for handling multiple message types
我的辦公桌上有 GOF,我知道一定有某種設計模式可以解決我遇到的問題,但是我想不通。
為了簡單起見,我更改了我正在使用的一些接口的名稱。
所以這就是問題所在,在網絡的一側,我有多個服務器發送不同類型的消息。 在網絡的另一端,我有一個客戶端需要能夠處理所有不同類型的消息。
所有消息都實現相同的通用接口 IMessage。 我的問題是,當客戶端收到一個新的 IMessage 時,它如何知道收到的是什么類型的 IMessage?
我想我可以做類似下面的事情,但這感覺很糟糕。
TradeMessage tMessage = newMessage as TradeMessage;
if (tMessage != null)
{
ProcessTradeMessage(tMessage);
}
OrderMessage oMessage = newMessage as OrderMessage;
if (oMessage != null)
{
ProcessOrderMessage(oMessage);
}
第二個想法是向 IMessage 添加一個名為 MessageTypeID 的屬性,但這需要我編寫如下內容,這也感覺很糟糕。
TradeMessage tMessage = new TradeMessage();
if (newMessage.MessageTypeID == tMessage.MessageTypeID)
{
tMessage = newMessage as TradeMessage;
ProcessTradeMessage(tMessage);
}
OrderMessage oMessage = new OrderMessage();
if (newMessage.MessageTypeID == oMessage.MessageTypeID)
{
oMessage = newMessage as OrderMessage;
ProcessOrderMessage(oMessage);
}
我知道這個一般問題已經被解決了一百萬次,所以必須有更好的方法來解決將接口作為參數的方法的問題,但需要基於 class 實現該接口的不同流量控制。
您可以為每種消息類型創建單獨的消息處理程序,並將消息天真地傳遞給每個可用的處理程序,直到找到可以處理它的消息處理程序。 與責任鏈模式類似:
public interface IMessageHandler {
bool HandleMessage( IMessage msg );
}
public class OrderMessageHandler {
bool HandleMessage( IMessage msg ) {
if ( !(msg is OrderMessage)) return false;
// Handle the message and return true to indicate it was handled
return true;
}
}
public class SomeOtherMessageHandler {
bool HandleMessage( IMessage msg ) {
if ( !(msg is SomeOtherMessage) ) return false;
// Handle the message and return true to indicate it was handled
return true;
}
}
... etc ...
public class MessageProcessor {
private List<IMessageHandler> handlers;
public MessageProcessor() {
handlers = new List<IMessageHandler>();
handlers.add(new SomeOtherMessageHandler());
handlers.add(new OrderMessageHandler());
}
public void ProcessMessage( IMessage msg ) {
bool messageWasHandled
foreach( IMessageHandler handler in handlers ) {
if ( handler.HandleMessage(msg) ) {
messageWasHandled = true;
break;
}
}
if ( !messageWasHandled ) {
// Do some default processing, throw error, whatever.
}
}
}
您還可以將其實現為映射,將消息類名稱或消息類型id作為鍵,並將相應的處理程序實例作為值。
其他人建議讓消息對象本身“處理”,但這對我來說感覺不對。 似乎最好將消息的處理與消息本身分開。
我喜歡的其他一些事情:
你可以通過spring或者what-have-you注入消息處理程序,而不是在構造函數中創建它們,這使得它非常容易測試。
通過簡單地從ProcessMessage循環中刪除“break”,您可以引入類似於主題的行為,其中您可以為單個消息提供多個處理程序。
通過將消息與處理程序分離,您可以在不同的目標處為同一消息提供不同的處理程序(例如,以不同方式處理相同消息的多個MessageProcessor類)
一些解決方案適用於此,首先是最佳解決方案,最后是最不好的解決方案。 所有示例都是偽代碼:
第1,最好的解決方案
Vincent Ramdhanie介紹了解決這個問題的實際正確模式,稱為策略模式 。
此模式創建一個單獨的“處理器”,在這種情況下,相應地處理消息。
但我很確定GOF的書中給出了一個很好的解釋:)
第2
如評論所述,消息可能無法自行處理,為消息或基類創建接口仍然很有用,因此您可以為消息創建一般處理函數,並為更具體的消息重載消息。
在任何情況下,重載都比為每種類型的消息創建不同的方法更好......
public class Message {}
public class TradeMessage extends Message {}
public class MessageProcessor {
public function process(Message msg) {
//logic
}
public function process(TradeMessage msg) {
//logic
}
}
第3
如果您的消息可以處理自己,您可以編寫一個接口,因為您的處理方法取決於您獲得的消息,將它放在消息類中似乎更容易...
public interface IMessage
{
public function process(){}
}
然后在所有消息類中實現它並處理它們:
list = List<IMessage>();
foreach (IMessage message in list) {
message.process();
}
在您的列表中,您可以存儲任何實現該接口的類...
根據我在消息處理方面的經驗,通常情況是消息的不同消費者需要處理各種消息類型。 我找到了Double Dispatch模式來很好地處理這個問題。 基本思想是注冊一組處理程序,這些處理程序將接收到的消息分派給處理程序,以便根據特定類型進行處理(使用函數重載)。 消費者只注冊他們希望收到的特定類型。 下面是一個類圖。
代碼如下所示:
IHandler
public interface IHandler
{
}
IMessageHandler
public interface IMessageHandler<MessageType> : IHandler
{
void ProcessMessage(MessageType message);
}
即時聊天
public interface IMessage
{
void Dispatch(IHandler handler);
}
MessageBase
public class MessageBase<MessageType> : IMessage
where MessageType : class, IMessage
{
public void Dispatch(IHandler handler)
{
MessageType msg_as_msg_type = this as MessageType;
if (msg_as_msg_type != null)
{
DynamicDispatch(handler, msg_as_msg_type);
}
}
protected void DynamicDispatch(IHandler handler, MessageType self)
{
IMessageHandler<MessageType> handlerTarget =
handler as IMessageHandler<MessageType>;
if (handlerTarget != null)
{
handlerTarget.ProcessMessage(self);
}
}
}
DerivedMessageHandlerOne
// Consumer of DerivedMessageOne and DerivedMessageTwo
// (some task or process that wants to receive messages)
public class DerivedMessageHandlerOne :
IMessageHandler<DerivedMessageOne>,
IMessageHandler<DerivedMessageTwo>
// Just add handlers here to process incoming messages
{
public DerivedMessageHandlerOne() { }
#region IMessageHandler<MessaegType> Members
// ************ handle both messages *************** //
public void ProcessMessage(DerivedMessageOne message)
{
// Received Message one, do something with it
}
public void ProcessMessage(DerivedMessageTwo message)
{
// Received Message two, do something with it
}
#endregion
}
DerivedMessageOne
public class DerivedMessageOne : MessageBase<DerivedMessageOne>
{
public int MessageOneField;
public DerivedMessageOne() { }
}
然后你就有了一個管理處理程序的容器,你就完成了。 收到消息時,處理程序列表的簡單循環,以及處理程序接收他們想要的消息
// Receive some message and dispatch it to listeners
IMessage message_received = ...
foreach(IHandler handler in mListOfRegisteredHandlers)
{
message_received.Dispatch(handler);
}
這個設計出自一個我回答多態事件處理的問題
一種選擇是讓消息帶有自己的處理程序。 也就是說,創建一個名為IMessageProcessor的接口,它指定方法processMessage(IMessage)。 接下來定義為每種類型的消息實現IMessageProcessor的具體類。
然后,每個IMessage類將定義自己的處理器。
當您收到消息對象時,您將執行以下操作:
message.processor.processMessage();
對於我在Silverlight應用程序中的小消息傳遞框架,我正在使用Mediator模式。 它是某種消息傳遞總線/代理,對象訂閱特定類型或類型的消息。 然后,這個Mediator對象(經紀人/總線)決定誰將收到什么樣的消息。
喜歡:
SubscribeFor<ChatMessage>().If(x=>x.SomeProp==true).Deliver(MyMethod);
調用的示例方法:
void MyMethod(ChatMessage msg) , or
void MyMethod(BaseBessage msg)
或發布(廣播)消息:
Publish(new ChatMessage());
BaseMessage是抽象類,我的所有消息都是繼承的,只是引用了發送者和一些獨特的Guid。
我從MVVM Light Toolkit構建我的消息傳遞框架的起點,你可以看看他們的源代碼,它並不復雜!
如果你問,我可以把c#代碼放在某個地方嗎?
將ProcessMessage()方法添加到iMessage接口,讓具體消息以多態方式決定自己處理的正確方法。
然后你的代碼變成了
newMessage.ProcessMessage();
這是一篇關於使用多態而不是條件的好文章。
您可能想看看Gregor Hohpe和Bobby Woolf的企業集成模式 。 它有一個很好的消息處理模式目錄。
調度模式可能運行良好。
public static class MessageDispatcher
{
private static readonly IMessageHandler s_DefaultHandler =
new DefaultMessageHandler();
private static readonly Dictionary<Type, IMessageHandler> s_Handlers =
new Dictionary<Type, IMessageHandler>();
static MessageDispatcher()
{
// Register a bunch of handlers.
s_Handlers.Add(typeof(OrderMessage), new OrderMessageHandler());
s_Handlers.Add(typeof(TradeMessage), new TradeMessageHandler());
}
public void Dispatch(IMessage msg)
{
Type key = msg.GetType();
if (s_Handlers.ContainsKey(key))
{
// We found a specific handler! :)
s_Handlers[key].Process(msg);
}
else
{
// We will have to resort to the default handler. :(
s_DefaultHandler.Process(msg);
}
}
}
public interface IMessageHandler
{
void Process(IMessage msg);
}
public class OrderMessageHandler : IMessageHandler
{
}
public class TradeMessageHandler : IMessageHandler
{
}
這個主題有各種各樣的變化。 它們都將包含一個包含許多不同處理程序的調度程序對象。 如果調度程序找不到特定的處理程序,您應該考慮使用默認處理程序。 您如何選擇將消息分發給適當的處理程序有很多自由。 我碰巧根據類型進行調度,但你可以使它任意復雜化。 也許調度員可以檢查消息的內容以發現最佳處理程序。 也許消息帶有一個標識首選處理程序的密鑰。 我不知道。 這里有很多可能性。
在類似的場景中,我有一台服務器,它接收來自多個客戶端的大量不同消息。
所有消息都以串行方式發送,並以消息類型的標識符開頭。 然后我有一個查看標識符的switch語句。 然后將消息反序列化(到非常不同的對象)並根據需要進行處理。
類似的事情可以通過傳遞實現接口的對象來完成,該接口包括指示消息類型的方式。
public void ProcessMessage(IMessage msg)
{
switch(msg.GetMsgType()) // GetMsgType() is defined in IMessage
{
case MessageTypes.Order:
ProcessOrder(msg as OrderMessage); // Or some other processing of order message
break;
case MessageTypes.Trade:
ProcessTrade(msg as TradeMessage); // Or some other processing of trade message
break;
...
}
}
我知道這是一個較舊的主題,多年來有很多非常好的答案。
然而,在2018年,我會使用Jimmy Bogard的MediatR( https://github.com/jbogard/MediatR )等軟件包。
它提供了消息發送和處理的解耦,例如請求/響應,命令/查詢,單向,發布/訂閱,異步,多態調度等。
我知道它太老了,但我今天不得不實施類似的東西,我只想對已接受的答案提一些有用的旁注,
首先,為了減少具體 Handle 實現中的代碼重復(is X,is Y),我建議創建一個抽象處理程序 class,因此:
public class OrderMessageHandler : IMessageHandler {
bool HandleMessage( IMessage msg ) {
if ( !(msg is OrderMessage)) return false;
// Handle the message and return true to indicate it was handled
return true;
}
}
public class SomeOtherMessageHandler : IMessageHandler {
bool HandleMessage( IMessage msg ) {
if ( !(msg is SomeOtherMessage) ) return false;
// Handle the message and return true to indicate it was handled
return true;
}
}
變成:
public abstract class MessageHandler<T> : IMessageHandler where T : IMessage
{
bool HandleMessage(IMessage msg)
{
if (!(msg is T concreteMsg)) return false;
Handle(concreteMsg);
return true;
}
protected abstract void Handle(T msg);
}
public class OrderMessageHandler : MessageHandler<OrderMessage>
{
protected override void Handle(OrderMessage msg)
{
// do something with the concrete OrderMessage type
}
}
public class SomeOtherMessageHandler : MessageHandler<SomeOtherMessage>
{
protected override void Handle(SomeOtherMessage msg)
{
// do something with the concrete SomeOtherMessage type
}
}
是的,我會考慮使用Dictionary<Type, IMessageHandler>
而不是foreach
並強制從處理中返回 bool 來決定它是否被處理,所以我的最終答案是:
*(ConcreteType 不是必須的,它可以幫助您在不指定類型的情況下添加處理程序)
public interface IMessageHandler
{
Type ConcreteType { get; }
void HandleMessage(IMessage msg);
}
public abstract class MessageHandlerBase<TConcreteMessage> : IMessageHandler where TConcreteMessage : IMessage
{
public Type ConcreteType => typeof(TConcreteMessage);
public void HandleMessage(IMessage msg)
{
if (msg is not TConcreteMessage concreteMsg) return;
Handle(concreteMsg);
}
protected abstract void Handle(TConcreteMessage msg);
}
public class OrderMessageHandler : MessageHandlerBase<OrderMessage>
{
protected override void Handle(OrderMessage msg)
{
// do something with the concrete OrderMessage type
}
}
public class SomeOtherMessageHandler : MessageHandlerBase<SomeOtherMessage>
{
protected override void Handle(SomeOtherMessage msg)
{
// do something with the concrete SomeOtherMessage type
}
}
public class MessageProcessor
{
private readonly Dictionary<Type, IMessageHandler> _handlers = new();
public MessageProcessor()
{
}
public void AddHandler(IMessageHandler handler)
{
var concreteMessageType = handler.ConcreteType;
if (_handlers.ContainsKey(concreteMessageType))
{
throw new Exception($"handler for type {concreteMessageType} already exists.");
//if you want to support multiple handlers for same type it can be solved with dictionary of List<T>
}
_handlers[concreteMessageType] = handler;
}
public void ProcessMessage(IMessage msg)
{
if (_handlers.TryGetValue(msg.GetType(), out var handler))
{
handler.HandleMessage(msg);
}
else
{
// Do some default processing, throw error, whatever.
}
}
}
public class OrderMessage : IMessage
{
public Guid Guid { get; set; }
public int Number { get; set; }
}
public class SomeOtherMessage : IMessage
{
public Guid Guid { get; set; }
public string Text { get; set; }
}
希望它可以幫助將來的人:)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.