【问题标题】:Design pattern for loading multiple message types加载多种消息类型的设计模式
【发布时间】:2010-06-19 02:48:23
【问题描述】:

当我浏览 SO 时,我遇到了一个关于 handling multiple message types 的问题。我担心的是 - 我如何以简洁的方式加载这样的消息?我决定有一个单独的类,它的方法每次调用时都会加载一条消息。此方法应创建具体消息类型(例如 AlphaMessage、BetaMessage、GammaMessage 等)的新实例并将其作为消息返回。

class MessageLoader
{
    public Message Load()
    {
        // ...
    }
}

方法中的代码对我来说看起来真的很糟糕,我非常想重构它/摆脱它:

Message msg = Message.Load(...); // load yourself from whatever source
if (msg.Type == MessageType.Alpha) return new AlphaMessage(msg);
if (msg.Type == MessageType.Beta) return new BetaMessage(msg);
// ...

事实上,如果整个设计看起来过于凌乱,而你们有更好的解决方案,我已经准备好重新构建整个设计。

如果我的描述过于混乱,请告诉我缺少什么,我将编辑问题。谢谢大家。

编辑: 我不喜欢这段代码的地方是我必须创建一个 Message 的实例(因为它知道如何加载自己),然后必须用具体的消息类型来装饰它(因为装饰器知道如何解释 msg 的 Data 属性)。也许这会使问题更清楚一些。

【问题讨论】:

  • 不确定您的问题是什么。这看起来像一个非常标准的工厂模式,您可以更改为 switch case 而不是 if 语句,但几乎是一样的。也许我误解了你的问题。
  • @CkH:我只是不喜欢我必须创建一个 Message 实例(因为它知道如何加载自己)然后必须用具体的消息类型来装饰它(因为装饰器知道如何解释 msg 的 Data 属性)

标签: c# .net design-patterns architecture


【解决方案1】:

我同意 CkH 的观点,即工厂模式将解决它。我写了一个愚蠢的例子作为概念证明。不是为了展示好的类设计,只是一个简单的工厂模式有效。即使您使用多种消息类型和处理程序,您也应该只需要稍微修改此模式。

class Class12
{
    public static void Main()
    {
        Message m = new Message(1, "Hello world");
        IMessageHandler msgHandler = Factory.GetMessageHandler(m.MessageType);
        msgHandler.HandleMessage(m);

        Message m2 = new Message(2, "Adios world");
        IMessageHandler msgHandler2 = Factory.GetMessageHandler(m2.MessageType);
        msgHandler2.HandleMessage(m2);
    }
}
public class Factory
{
    public static IMessageHandler GetMessageHandler(int msgType)
    {
        IMessageHandler msgHandler = null;
        switch(msgType)
        {
            case 1: 
                msgHandler = new MessageHandler1();
                break;
            case 2: 
                msgHandler = new MessageHandler2();
                break;
            default: 
                msgHandler = new MessageHandler1();
                break;
        }
        return msgHandler;
    }
}
public class Message
{
    public int MessageType { get; set; }
    public string AMessage { get; set; }

    public Message(int messageType, string message)
    {
        this.MessageType = messageType;
        this.AMessage = message;
    }
}
public interface IMessageHandler
{
    void HandleMessage(Message m);
}
class MessageHandler1 : IMessageHandler
{

    #region IMessageHandler Members


    public void HandleMessage(Message m)
    {
        string message = m.AMessage;
        Console.WriteLine(message);
    }

    #endregion
}
class MessageHandler2 : IMessageHandler
{

    #region IMessageHandler Members


    public void HandleMessage(Message m)
    {
        string message = m.AMessage;
        Console.WriteLine("Hey there " + message);
    }

    #endregion
}

【讨论】:

  • 这是一个很好的例子,谢谢。看,当谈到消费消息时,我实际上确实有一个调度类,它与你的带有处理程序的工厂非常相似。正如 Dan Bryant 很好地描述的那样,我试图通过包装消息来实现添加解释功能。所以我得到了:1) 底层 - 带有标头和原始有效负载的消息 2) 中间层 - 实际上是一些具体文档的装饰消息 3) 顶层 - 一组以不同方式处理每个文档的处理程序
【解决方案2】:

下一个抽象层次是使消息发现和实例化动态化。这通常通过将字符串名称与每个消息相关联或使用类的名称作为标识符来实现。您可以使用反射来发现可用的消息类型,将它们存储在字典中并按名称提供实例化。这可以进一步扩展以从动态加载的“插件”程序集中引入消息,具有适当的元数据和接口,以允许不同消息和消息使用者之间的松散耦合组合。一旦达到这个水平,我建议您研究像 MEF 这样的框架,它可以自动执行发现和实例注入过程。

对于您的简单应用程序,我认为您的方法已经很干净了。一系列 if 语句或 switch 可以正常工作,并且易于理解/维护,只要您有一组相对较小且稳定的案例。


总结cmets中的进一步讨论:

造成不安的主要设计问题是,从 Message 继承的不同特定消息以及基础 Message 必须在更具体的消息执行进一步分析之前实例化。这混淆了 Message 是打算包含原始信息还是充当解释消息的基本类型。更好的设计是将 RawMessage 功能分离到自己的类中,明确分离关注点并解决“实例化两次”的感觉。

至于使用 DTO 和映射器类进行重构:

我实际上更喜欢您的应用特定消息编码/解码方法。如果我想追查为什么 FactoryTakenOverByRobotsMessage 包含无效数据,对我来说,消息的解析器方法包含在消息的解码数据中是有意义的。如果您想要支持不同的编码,事情会变得更加冒险,因为现在您开始想要以声明方式指定 DTO(例如使用属性)并允许您的不同传输层决定如何序列化/反序列化。然而,在我使用您的模式的大多数情况下,它是针对一个非常特定于应用程序的情况,通常有些不一致的消息格式和各种无法以任何自动方式很好地映射的专有编码。我仍然可以始终将声明性编码与专有的类内编码并行使用,并执行诸如将我的消息序列化为 XML 以进行调试之类的事情。

【讨论】:

  • 所以每条消息实际上都有一个字符串类型 id,但我将它封装成某种“字符串枚举”,可能只是为了具有智能感知。我喜欢使用字典来实例化具体消息类型的建议,但正如你所说,由于这是一个简单的应用程序,我可以使用 if 树。当我看到每条消息必须以某种方式实例化两次时,我只是抽搐。看起来不错?
  • 由于第二个实例化是一个装饰器,它增加了解释功能,我认为这很好。基本上,您只是通过额外的处理来处理原始消息以提取更多有用的信息。尴尬的主要事情是,您再次将其作为消息返回,因此您最终会在其他地方再次检查消息类型。如果各种 Message 详细信息不源自 Message 并且 Message 被重命名为 RawMessage 之类的东西,这可能更有意义。
  • 这正是我正在做的! “解释性功能” - 说得真好。用 C# 实际表达整个想法比用简单的英语要容易得多,但这只是一个旁注。你不是从Message下降是什么意思?顺便提一句。我得到的是一种队列,我在其中保存所有需要处理的消息,这就是为什么我决定最好将它们作为消息返回,而不仅仅是一个对象。我知道在消息传递中一种常见的方法是使用类型队列,但我需要按 FIFO 顺序处理它们,所以我认为一个更好。抱歉,评论太长了
  • 你这里真的有两个不同的概念。第一个是原始消息,而第二个是解释消息。它们实际上是两种不同类型的对象,解释的消息不一定是“一种”原始消息。解释的消息可能包括对其解释的原始消息的引用,这可以缓解对象以某种方式“实例化两次”的抽搐感觉。
  • "ConcreteMessage is a RawMessage" - 只是名称稍有变化,整个句子现在听起来确实很不靠谱。会不会是另一种情况,继承有点强迫,而组合实际上是一个更好的解决方案?应用这个想法可能意味着将一些代码颠倒过来,但这听起来很合理,我可以尝试一下。任何可以阻止抽搐的方法 ;) 感谢您的洞察力!
【解决方案3】:

使用 C#,您可能需要类似您编写的内容,因为 C# 是一种强类型语言。基本上,您必须在代码中的某个位置获取具体类。

【讨论】:

    【解决方案4】:

    你所拥有的看起来不错。这是明确的。如果您的 AlphaMessage 和 BetaMessage 对象是 Message 的子对象,则无需创建新对象,只需返回已转换的对象即可。

    if (msg.Type == MessageType.Alpha) return msg as AlphaMessage;
    else if (msg.Type == MessageType.Beta) return msg as BetaMessage;
    

    消费者代码必须处理转换失败的情况(as 返回 null)。

    【讨论】:

    • 嗯,AlphaMessage 和 BetaMessage 是 Message 的子级,但 Message 不是抽象类 - Message.Load() 实际上创建了该类的实例,稍后必须将其“转换”为具体的消息类型,具有特定的属性。我真的仍然可以对其进行贬低(而不是包装)吗?我有点难以在这里表达出来,对此感到抱歉。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-07
    • 2014-10-15
    • 1970-01-01
    • 1970-01-01
    • 2022-08-14
    • 2014-09-19
    • 2013-07-26
    相关资源
    最近更新 更多