Reputation: 743
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
Upvotes: 2
Views: 2060
Reputation: 54734
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);
}
}
Upvotes: 4
Reputation: 11577
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/
Upvotes: 0
Reputation: 59111
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.
Upvotes: 3