我已经把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);
}
我知道这个普遍的问题已经被解决了一百万次,所以必须有一种更好的方法来解决一个方法将接口作为参数的问题,但是需要基于什么类实现的不同的流控制那个界面。
答案 0 :(得分:20)
您可以为每种消息类型创建单独的消息处理程序,并将消息天真地传递给每个可用的处理程序,直到找到可以处理它的消息处理程序。类似于责任链模式:
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或者有什么来注入消息处理程序,而不是在构造函数中创建它们,这使得它非常可测试。
您可以通过简单地从ProcessMessage循环中删除“break”来引入类似于主题的行为,其中您可以为单个邮件提供多个处理程序。
通过将消息与处理程序分开,您可以在不同的目标上为同一消息提供不同的处理程序(例如,以不同方式处理相同消息的多个MessageProcessor类)
答案 1 :(得分:15)
有两种解决方案适用于此,首先是最佳解决方案,最后是最不好的解决方案。所有示例都是伪代码:
第一,最佳解决方案
Vincent Ramdhanie介绍了解决这个问题的实际正确模式,称为strategy pattern。
此模式创建一个单独的“处理器”,在这种情况下,相应地处理消息。
但我很确定GOF在你的书中给出了一个很好的解释:)
<强>第二强>
如评论所述,消息可能无法自行处理,为消息或基类创建接口仍然很有用,因此您可以为消息创建一般处理函数,并将其重载为更多特定的。
重载在任何情况下都比为每种类型的消息创建不同的方法更好......
public class Message {}
public class TradeMessage extends Message {}
public class MessageProcessor {
public function process(Message msg) {
//logic
}
public function process(TradeMessage msg) {
//logic
}
}
<强>第三强>
如果您的消息可以自行处理,您可以编写一个接口,因为您的处理方法取决于您获得的消息,将它放在消息类中似乎更容易......
public interface IMessage
{
public function process(){}
}
然后在所有消息类中实现它并处理它们:
list = List<IMessage>();
foreach (IMessage message in list) {
message.process();
}
在列表中,您可以存储实现该接口的任何类...
答案 2 :(得分:8)
根据我在消息处理方面的经验,通常情况是消息的不同消费者需要处理各种消息类型。我找到了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() { }
}
然后你只有一个管理处理程序的容器,你就完成了。收到消息时Handler列表的简单for循环,Handler接收他们想要的消息
// Receive some message and dispatch it to listeners
IMessage message_received = ...
foreach(IHandler handler in mListOfRegisteredHandlers)
{
message_received.Dispatch(handler);
}
这个设计来自我前一段时间回答Polymorphic Event Handling
的问题答案 3 :(得分:4)
一种选择是让消息附带自己的处理程序。也就是说,创建一个名为IMessageProcessor的接口,它指定方法processMessage(IMessage)。接下来定义为每种消息类型实现IMessageProcessor的具体类。
然后,每个IMessage类将定义自己的处理器。
当您收到消息对象时,您将执行以下操作:
message.processor.processMessage();
答案 4 :(得分:3)
对于我在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#代码放在某个地方吗?
答案 5 :(得分:2)
将一个ProcessMessage()方法添加到iMessage接口,让具体的消息以多种方式决定自己处理的正确方法。
您的代码将变为
newMessage.ProcessMessage();
这是一篇关于using polymorphism instead of conditionals的好文章。
答案 6 :(得分:2)
你可能想看看Gregor Hohpe和Bobby Woolf的Enterprise Integration Patterns。它有一个很好的消息处理模式目录。
答案 7 :(得分:2)
调度模式可能效果很好。
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
{
}
这个主题有各种各样的变化。它们都将包含一个包含许多不同处理程序的调度程序对象。如果调度程序找不到特定的处理程序,您应该考虑使用默认处理程序。您如何选择将消息分发给适当的处理程序有很多自由。我碰巧根据类型进行调度,但你可以使它任意复杂化。也许调度员可以检查消息的内容以发现最佳处理程序。也许消息带有一个标识首选处理程序的密钥。我不知道。这里有很多可能性。
答案 8 :(得分:1)
我知道这是一个较旧的主题,多年来有很多非常好的答案。
然而,在2018年,我会使用Jimmy Bogard的MediatR(https://github.com/jbogard/MediatR)这样的包。
它提供了消息发送和处理的解耦,例如请求/响应,命令/查询,单向,发布/订阅,异步,多态调度等。
答案 9 :(得分:0)
在类似的场景中,我有一台服务器,它接收来自多个客户端的大量不同消息。
所有消息都以串行方式发送,并以消息类型的标识符开头。然后我有一个查看标识符的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;
...
}
}