views:

143

answers:

3

Hi,

I have a base class:

class Message

And two deriving classes:

class SimpleMessage : Message
class ComplexMesssage : Message

These types are used in another part of the code as such:

void ProcessSimpleMessage(SimpleMessage m)
void ProcessComplexMessage(ComplexMessage m)

These methods are not inside the class Message, as the processing is not part of the message.

Now, i would like to avoid an if/else/switch structure, because there are many types of messages. What is the best design pattern to use here?

One option is to encapsulate using the strategy pattern (at least as i understand it):

class ProcessableMessage
{
delegate void ProcessMessageDelegate(Message m)

private Message m;
private ProcessMessageDelegate ProcessMessage;
}

But is it really best practice to make all the processing methods accept the base type Message, and cast inside? And another problem would be with the fact that the dynamic type of the message (simple or complex) is actually stored in 2 places in this class - the message and the process algorithm, which seems kind of ugly.

Any better solutions out there?

Thanks!!
Assaf

+3  A: 

Why not just add the virtual method:

class Message
{
    public abstract void Process();
}

If you really need to keep the code separated:

class Message
{
    public abstract void Process();
}

class SimpleMessage
{
    public override void Process()
    {
        new SimpleMessageProcessor().Process();
    }
}

class SimpleMessageProcessor
{
    internal void Process()
    {
        // ...
    }
}

I mean, what sort of flexibility do we need here? What other classes are involved? What is your surrounding scenario? Without any other context, this is really the simplest method to understand, and easiest to implement. Sometimes people add design cruft when it really isn't needed.

The strategy pattern is generally for if you wanted to have different methods to process the same message type, and wanted to switch them at runtime. If one type of processing generally goes with one type of message, then you don't need to make it any more complicated.

Merlyn Morgan-Graham
For design purposes, the processing methods cant be inside the Message class. This solution is possible using a wrapper, but, The processing methods signatures are different - This forces the first line in each process method to be a cast: SimpleMessage sm = (SimpleMessage) m_Message;
Assaf
What i want to avoid here is code that looks like this:if (typeof(message) == SimpleMessage) ProcessSimpleMessage((SimpleMessage) message))if (typeof(message) == ComplexMessage) ProcessComplexMessage((ComplexMessage) message)).....
Assaf
It's not uncommon for messages to have different ways process it. Think for instance the windows message for mouse click. The behavior differs depending on who listens to it.
FuleSnabel
As for your second comment, yeah that's what virtual methods are for... Though I guess it is how you insert the virtual method that you are interested in. If the signatures for processing the messages are different, then shouldn't those extra parameters live in the message class? Where do those parameters come from?
Merlyn Morgan-Graham
Merlyn - you're absolutely correct. The classic solution here would be to add a virtual Process() to the base class, and have it implemented differently in the derived classes. But the thing is that I cant touch all these message classes. The crux of the matter here is the correct way to wrap/encapsulate them.
Assaf
@Assaf: If you don't have control of the tree of message classes, and you're getting references of the type of the base class, there is no way to avoid the else-if. You could use a dictionary instead of an else-if and you'd get both a performance benefit, and the benefit of making your code more declarative. The value type in the dictionary could either be the type that you use to wrap the messages (adapter pattern), or the method (delegate) to be used to process the message.
Merlyn Morgan-Graham
@FuleSnabel: Win32 messages are not very OO at all. When messages like that get wrapped in higher-level code, they tend to have a completely different approach, such as eventing, signals/slots, overloaded methods in a type that reacts to the message, etc. I know he's dealing w/ the same sort of thing, but really it depends on his existing code, which he has told us very little about.
Merlyn Morgan-Graham
@Merlyn: Just a note around message based designs such as Win32 Message loop, SOAP, Erlang, smalltalk or emails. Message based designs are robust,flexible and provide good encapsulation. IMO often when developers tries to add a OO layer on top of a message based design they often wrap away whats good about Message Based design. With win32 just look at MFC or WinForms, especially WinForms does a poor job. With SOAP look at WCF or any other webservice framework. Message based designs stand the test of time. Just look at Win32 message loop / Email / Erlang.
FuleSnabel
@FuleSnabel: I am not saying that he should get rid of the message based design, because they are important. I am saying that message based designs don't work as well at a higher level of abstraction. I am not able to talk to smalltalk or erlang, but the rest of your examples involve a relatively low level serialization protocol. At that level, complete, accurate and efficient transmission of the message is the highest priority, rather than how you respond to the message. When you are trying to abstract at a higher level, higher level constructs will work better.
Merlyn Morgan-Graham
@FuleSnabel: Also, not to be argumentative, I just think we have something interesting to talk about here... what is wrong with MFC/Window Forms? Most of the complaints I have heard about MFC was its size compared to win32, not the design. Then many people were happy with Window Forms. Don't get me wrong, WPF solves the problems *I* was interested in solving (UI encapsulation for maintenance, clear design, and testability purposes), but I can't say the same for your run of the mill developer. What is the huge drawback to Window Forms compared to Win32?
Merlyn Morgan-Graham
+3  A: 

I'd use the visitor pattern here:

public interface IMessageVisitor
{
    void VisitSimple(SimpleMessage msg);
    void VisitComplex(ComplexMessage msg);
}

public abstract class Message
{
    public abstract void Accept(IMessageVisitor visitor);
}

public class SimpleMessage : Message
{
    public override void Accept(IMessageVisitor visitor)
    {
        visitor.VisitSimple(this);
    }
}

public class ComplexMessage : Message
{
    public override void Accept(IMessageVisitor visitor)
    {
        visitor.VisitComplex(this);
    }
}

public class MessageProcessor : IMessageVisitor
{
    void IMessageVisitor.VisitSimple(SimpleMessage msg)
    { process simple message }

    void IMessageVisitor.VisitComplex(ComplexMessage msg)
    { process complex message }

    public void Process(Message msg)
    {
        msg.Accept(this);
    }
}
Tim Robinson
where do you implemet the abstract method Accept?
Mustafa A. Jabbar
On `SimpleMessage` and `ComplexMessage`. It's a one-line method, which calls either `visitor.VisitSimple(this)` or `visitor.VisitComplex(this)`.
Tim Robinson
A: 

I like the Visitor approach above. However just for the fun of it I show a bit on how to reduce redundancy in code using T4 in VS2008 and VS2010.

The redundancy comes from that for each message you need a Visit method. Also each method needs a simple but redundant implementation of Accept. One way to get closer to "Do not repeat yourself" is generating the code using T4.

In order to test the following sample add a class in VS but change the extension from .cs to .tt. You will now get two files a .tt file and a .cs file connected to the .tt file.

The .tt file is a template that generates .cs file. At the time they are identical.

Use this as the content for the .tt file:

<#@ template language="C#" #>
<#
   // On VS2008 change C# above to C#v3.5

   // -----------------------------------------------------
   // Here we declare our different message types
   var messageTypes = new []
      {
         "Simple",
         "Complex",
         "Other",
      };
   // -----------------------------------------------------
#>

namespace MessageProcessor
{
   partial interface IMessageVisitor
   {
<#
   // Let's generate all message visitor methods
   foreach (var messageType in messageTypes)
   {
#>
      void Visit (<#=messageType#>Message message);
<#
   }
#>
   }
   abstract partial class Message
   {      
      public abstract void Accept (IMessageVisitor visitor);
   }
<#
   // Let's generate all message types
   foreach (var messageType in messageTypes)
   {
#>
   sealed partial class <#=messageType#>Message : Message
   {      
      public override void Accept (IMessageVisitor visitor)
      {
         visitor.Visit (this);
      }
   }
<#
   }
#>
}

This should generate a CS file that looks like this:

namespace MessageProcessor
{
   partial interface IMessageVisitor
   {
      void Visit (SimpleMessage message);
      void Visit (ComplexMessage message);
      void Visit (OtherMessage message);
   }
   abstract partial class Message
   {      
      public abstract void Accept (IMessageVisitor visitor);
   }
   sealed partial class SimpleMessage : Message
   {      
      public override void Accept (IMessageVisitor visitor)
      {
         visitor.Visit (this);
      }
   }
   sealed partial class ComplexMessage : Message
   {      
      public override void Accept (IMessageVisitor visitor)
      {
         visitor.Visit (this);
      }
   }
   sealed partial class OtherMessage : Message
   {      
      public override void Accept (IMessageVisitor visitor)
      {
         visitor.Visit (this);
      }
   }
}

Why is this less redudant? Because now whenever I like to add a new meessage I just add it to the template:

   var messageTypes = new []
      {
         "Simple",
         "Complex",
         "Other",
         "YetAnotherOne",
      };

It's important to note that all messages are generated as partial because we need different payloads for a message. This is specified in another file and it could look like this:

   partial class SimpleMessage
   {
      public string Name;
   }

   partial class ComplexMessage
   {
      public XmlDocument Xml;
   }

For those that likes the sound of T4 check this blog: http://www.olegsych.com/2008/09/t4-tutorial-creatating-your-first-code-generator/

FuleSnabel
I noticed you say that you can't change Message classes. Well you still can autogenerate the visitor class using T4.
FuleSnabel
Thanks! I had no idea this even existed.
Assaf