Design pattern for loading multiple message types

后端 未结 4 1464
攒了一身酷
攒了一身酷 2021-01-06 10:29

As I was looking through SO I came across a question about handling multiple message types. My concern is - how do I load such a message in a neat way? I decided to have a s

相关标签:
4条回答
  • 2021-01-06 10:35

    I agree with CkH in that Factory pattern will solve it. I wrote a silly example as a proof of concept. Not meant to show good class design, just that a simple Factory pattern works. Even if you are using multiple message types and handlers, you should only need to modify this pattern slightly.

    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
    }
    
    0 讨论(0)
  • With C# you'll probably need something like what you've written because C# is a strongly-typed language. Basically, you have to get the concrete classes somewhere in your code.

    0 讨论(0)
  • 2021-01-06 10:49

    What you have looks fine. It's unambiguous. If your AlphaMessage and BetaMessage objects are children of Message then instead of creating a new object, just return the object casted.

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

    Consumer code will have to handle the case where the cast fails (as returns null).

    0 讨论(0)
  • 2021-01-06 10:51

    The next level of abstraction is to make Message discovery and instantiation dynamic. This is often accomplished by associating a string name with each Message or by using the name of the class as an identifier. You can use Reflection to discover available Message types, store them in a Dictionary and provide instantiation by name. This can be further extended to bring in Messages from dynamically loaded 'plugin' assemblies, with appropriate meta-data and interfaces to allow for loosely coupled composition between different Messages and Message Consumers. Once you get to that level, I recommend looking into frameworks like MEF which automate the discovery and instance injection process.

    For your simple application, I think your approach is already quite clean. A series of if statements or a switch works just fine and is easy to understand/maintain, as long as you have a relatively small and stable set of cases.


    Summarizing the further discussion in the comments:

    The main design concern creating uneasiness was the fact that the different specific messages inherited from Message and yet a base Message had to be instantiated before the more specific messages could perform further analysis. This muddied up whether the Message is intended to contain raw information or to act as a base type for interpreted messages. A better design is to separate the RawMessage functionality into its own class, clearly separating concerns and resolving the feeling of 'instantiating twice'.

    As for refactoring with DTOs and a mapper class:

    I actually prefer your approach for an app-specific message encoding/decoding. If I want to track down why FactoryTakenOverByRobotsMessage contains invalid data, it makes sense to me that the parser method for the message is contained with the decoded data for the message. Where things get more dicey if when you want to support different encodings, as now you start wanting to specify the DTO declaratively (such as with attributes) and allow your different transport layers to decide how to serialize/deserialize. In most cases where I'm using your pattern, however, it's for a very app-specific case, with often somewhat inconsistent message formats and various proprietary encodings that don't map well in any automatic way. I can always still use the declarative encoding in parallel with the proprietary, in-class encoding and do things like serialize my messages to XML for debugging purposes.

    0 讨论(0)
提交回复
热议问题