考虑我正在与将以某种格式发送消息(数据库表,消息队列,Web服务)的外部系统连接。在“消息头”中有“MessageType”,它是1到20之间的数字.MessageType定义了如何处理消息的其余部分。有新的,修改的,删除的,取消的......
我的第一个倾向是设置枚举并定义所有类型。然后将数字解析为枚举类型。将它作为枚举,我将设置典型的开关案例系统,并为每种消息类型调用特定的方法。
一个重要的问题是维护 开关/箱体系统笨重而且很笨重,但它非常简单 各种其他表/配置系统可能很难让其他人知道并添加新消息或调整现有消息。
对于12个左右的MessageTypes,交换机/案例系统似乎很合理。切换到表驱动系统的合理截止点是什么?
哪种系统最适合处理这些类型的问题?
我在这里为C#和Java设置了一个标签,因为它绝对是一个常见的问题。还有许多其他语言存在同样的问题。
答案 0 :(得分:5)
在Java中,你可以把它作为枚举并给不同值的行为(尽管有100个值,我希望每种类型的行为都是短暂的,呼唤“正确的”类)。
在C#中,您可以拥有从值到某个适当委托类型的映射 - 然后在静态构造映射时,您可以根据需要使用lambda表达式或方法组转换。
话虽如此,设置地图将像switch语句一样丑陋。如果每个switch语句只是一个方法调用,您可能想尝试这种格式:
switch (messageType)
{
case 0: HandleLogin(message); break;
case 50: SaveCurrentDocument(message); break;
case 100: HandleLogout(message); break;
}
(等)。我知道这违反了正常惯例,但对于像这样的奇怪异常情况它可能非常简洁。如果你只需要在一个地方使用数字,那么引入常量几乎没有意义 - 基本上包含有效数字的行是常量定义!
答案 1 :(得分:1)
如何让Dictionary<MessageType, ProcessMessageDelegate>
按照消息类型存储这些方法?在类的初始化期间,注册此字典中的所有方法。然后调用适当的方法。以下是伪代码:
delegate void ProcessMessageDelegate(Message message)
public class MyMessageProcessor
{
Dictionary<int, ProcessMessageDelegate> methods;
public void Register( int messageType,
ProcessMessageDelegate processMessage)
{
methods[messageType] = processMessage;
}
public void ProcessMessage(int messageType, Message message)
{
if(methods.ContainsKey(messageType))
{
methods[messageType](message);
}
}
}
注册方法:
myProcessor.Register(0, ProcessMessageOfType0);
myProcessor.Register(1, ProcessMessageOfType1);
myProcessor.Register(2, ProcessMessageOfType2);
...
编辑:我意识到Jon已经建议制作一张现在让我的答案多余的地图。但我不明白为什么静态构造的地图比切换案例更丑陋?
答案 2 :(得分:1)
拥有这样的处理程序界面:
interface MessageHandler {
void processMessage(Message msg) throws Exception;
int[] queryInterestingMessageIds();
int queryPriority(int messageId); // this one is optional
}
找到,实例化并注册您的处理程序。您可能希望使用一些基于反射的机制,如ServiceLoader,Spring(显式配置或classpath scanning可能自动装配)或普通属性文件
注册应该将每个处理程序传递给WhateverManager类,该类将在内部保存处理程序集合的映射(或由消息ID索引的arry)。如果您希望有多个处理程序,则可以使用 queryPriority(int)方法来解决处理顺序(否则您可以将其视为错误并在配置时抛出异常)。不使用静态地图进行注册是一种很好的做法。
如果您决定为邮件支持多个处理程序,则可能需要菊花链它们。在这种情况下,一种方法是更改签名,如下所示:
Message processMessage(Message msg, Message original) throws Exception;
答案 3 :(得分:0)
这就是我在C#中的表现。
我认为这种方法实际上并不是那么丑陋,随着消息类型数量的增加而变得不那么难看:要实现新的消息类型,您只需要为Enum添加一个值并标记新消息带有属性的处理程序类。
在某些情况下,能够在运行时从程序集加载消息处理程序是一个非常强大的功能;根据安装的消息处理程序程序集,您可以拥有一个行为不同的可执行文件。
首先为消息处理程序创建一个接口(我们称之为IMessageHandler
),为消息类型创建一个Enum(我们称之为MessageType
)。
接下来,创建一个名为MessageHandlerAttribute
的类:
public class MessageHandlerAttribute : System.Attribute
{
public MessageType MessageType { get; set; }
}
现在将每个消息处理程序实现为一个单独的类,并使用其消息类型属性标记每个类。如果消息处理程序可以处理多种类型的消息,则可以在其上放置多个属性:
[MessageHandler(MessageType=MessageType.Login)]
public class LoginMessageHandler : IMessageHandler
{
...
}
这些消息处理程序都具有无参数构造函数,这一点很重要。我想不出一个很好的理由,你想要一个消息处理程序的构造函数来获取参数,但如果有的话,下面的代码就无法处理它。
将所有消息处理程序构建到同一个程序集中,并确保您有办法在运行时知道其路径。 (这是这种方法失败的重点。)
现在我们可以使用Reflection在运行时构建消息处理程序的映射:
using System.Reflection;
...
Assembly mhAssembly = Assembly.LoadFrom(mhAssemblyPath);
Dictionary<MessageType, IMessageHandler> mhMap = new Dictionary<MessageType, IMessageHandler>();
foreach (Type t in mhAssembly.GetExportedTypes())
{
if (t.GetInterface("IMessageHandler") != null)
{
MessageHandlerAttribute list = (MessageHandlerAttribute[])t.GetCustomAttributes(
typeof(MessageHandlerAttribute), false);
foreach (MessageHandlerAttribute att in list)
{
MessageType mt = att.MessageType;
Debug.Assert(!mhMap.ContainsKey(mt));
IMessageHandler mh = mhAssembly.CreateInstance(
t.FullName,
true,
BindingFlags.CreateInstance,
null,
new object[] { },
null,
null);
mhMap.Add(mt, mh);
}
}
// depending on your application, you might want to check mhMap now to make
// sure that every MessageType value is in it.
}
return mhMap;
现在当你收到消息时,你可以像这样处理它:
Debug.Assert(MhMap.ContainsKey(Message.MessageType));
IMessageHandler mh = MhMap[Message.MessageType];
mh.HandleMessage(Message);
此代码全部基于我现在在生产系统中的代码;我稍微改了一下(以便消息处理程序实现一个接口而不是从一个抽象类派生,并且它处理多个消息处理程序属性),这可能会在其中引入错误。