Design pattern for handling multiple message types

Viewed 40681

I've got the GOF sitting on my desk here and I know there must be some kind of design pattern that solves the problem I'm having, but man I can't figure it out.

For simplicities sake, I've changed the name of some of the interfaces that I'm using.

So here's the problem, on one side of the wire, I've got multiple servers that send out different types of messages. On the other side of the wire I have a client that needs to be able to handle all the different types of messages.

All messages implement the same common interface IMessage. My problem is, when the client gets a new IMessage, how does it know what type of IMessage its received?

I supposed I could do something like the following, but this just FEELS awful.

TradeMessage tMessage = newMessage as TradeMessage;
if (tMessage != null)
{
    ProcessTradeMessage(tMessage);
}

OrderMessage oMessage = newMessage as OrderMessage;
if (oMessage != null)
{
    ProcessOrderMessage(oMessage);
}

The second thought, is to add a property to IMessage called MessageTypeID, but that would require me to write something like the following, which also FEELS awful.

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);
}

I know this general problem has been tackled a million times, so there has to be a nicer way of solving the problem of having a method that takes an interface as a parameter, but needs different flow control based on what class has implemented that interface.

11 Answers

I know this is an older thread, with several very good answers over the years.

However, in 2018, I'd use a package such as Jimmy Bogard's MediatR (https://github.com/jbogard/MediatR).

It provides decoupling of message sending and processing with patterns such as request/response, Command/Query, One-way, Pub/Sub, async, polymorphic dispatching, etc.

I know it's super old, but I had to implement something similar today and I just wanted to mention some little useful sidenotes to the accepted answer,

first, to reduce code duplications (is X, is Y) in the concrete Handle implementation, I would recommend making an abstract handler class, so this:

public class OrderMessageHandler : IMessageHandler {
    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 : IMessageHandler {
    bool HandleMessage( IMessage msg ) {
       if ( !(msg is SomeOtherMessage) ) return false;

       // Handle the message and return true to indicate it was handled
       return true;
    }
}

becomes:

public abstract class MessageHandler<T> : IMessageHandler where T : IMessage
{
    bool HandleMessage(IMessage msg)
    {
        if (!(msg is T concreteMsg)) return false;
        Handle(concreteMsg);
        return true;
    }
    protected abstract void Handle(T msg);
}
public class OrderMessageHandler : MessageHandler<OrderMessage>
{
    protected override void Handle(OrderMessage msg)
    {
        // do something with the concrete OrderMessage type
    }
}
public class SomeOtherMessageHandler : MessageHandler<SomeOtherMessage>
{
    protected override void Handle(SomeOtherMessage msg)
    {
        // do something with the concrete SomeOtherMessage type
    }
}

and yea I would consider using Dictionary<Type, IMessageHandler> instead of foreach and forcing to return bool from handling to decide if it was handled , so my final answer would be:

*(ConcreteType is not a must, it's there to help you to add the handler without specifying the type)

public interface IMessageHandler
{
    Type ConcreteType { get; }
    void HandleMessage(IMessage msg);
}

public abstract class MessageHandlerBase<TConcreteMessage> : IMessageHandler where TConcreteMessage : IMessage
{
    public Type ConcreteType => typeof(TConcreteMessage);
    public void HandleMessage(IMessage msg)
    {
        if (msg is not TConcreteMessage concreteMsg) return;
        Handle(concreteMsg);
    }
    protected abstract void Handle(TConcreteMessage msg);
}

public class OrderMessageHandler : MessageHandlerBase<OrderMessage>
{
    protected override void Handle(OrderMessage msg)
    {
        // do something with the concrete OrderMessage type
    }
}

public class SomeOtherMessageHandler : MessageHandlerBase<SomeOtherMessage>
{
    protected override void Handle(SomeOtherMessage msg)
    {
        // do something with the concrete SomeOtherMessage type
    }
}

public class MessageProcessor
{
    private readonly Dictionary<Type, IMessageHandler> _handlers = new();

    public MessageProcessor()
    {
    }
    public void AddHandler(IMessageHandler handler)
    {
        var concreteMessageType = handler.ConcreteType;
        if (_handlers.ContainsKey(concreteMessageType))
        {
            throw new Exception($"handler for type {concreteMessageType} already exists.");
            //if you want to support multiple handlers for same type it can be solved with dictionary of List<T>
        }
        _handlers[concreteMessageType] = handler;
    }

    public void ProcessMessage(IMessage msg)
    {
        if (_handlers.TryGetValue(msg.GetType(), out var handler))
        {
            handler.HandleMessage(msg);
        }
        else
        {
            // Do some default processing, throw error, whatever.
        }
    }
}

public class OrderMessage : IMessage
{
    public Guid Guid { get; set; }
    public int Number { get; set; }
}

public class SomeOtherMessage : IMessage
{
    public Guid Guid { get; set; }
    public string Text { get; set; }
}

Hope it can help someone in the future :)

Related