最佳表/枚举驱动方法调用系统

时间:2008-10-17 22:13:14

标签: c# java

考虑我正在与将以某种格式发送消息(数据库表,消息队列,Web服务)的外部系统连接。在“消息头”中有“MessageType”,它是1到20之间的数字.MessageType定义了如何处理消息的其余部分。有新的,修改的,删除的,取消的......

我的第一个倾向是设置枚举并定义所有类型。然后将数字解析为枚举类型。将它作为枚举,我将设置典型的开关案例系统,并为每种消息类型调用特定的方法。

一个重要的问题是维护 开关/箱体系统笨重而且很笨重,但它非常简单 各种其他表/配置系统可能很难让其他人知道并添加新消息或调整现有消息。

对于12个左右的MessageTypes,交换机/案例系统似乎很合理。切换到表驱动系统的合理截止点是什么?

哪种系统最适合处理这些类型的问题?

我在这里为C#和Java设置了一个标签,因为它绝对是一个常见的问题。还有许多其他语言存在同样的问题。

4 个答案:

答案 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);

此代码全部基于我现在在生产系统中的代码;我稍微改了一下(以便消息处理程序实现一个接口而不是从一个抽象类派生,并且它处理多个消息处理程序属性),这可能会在其中引入错误。