I've seen this problem come up a lot, but never adequately handled, and I haven't seen it on Stack Overflow, so here goes. I wish there were a way to put this shortly and succinctly without lacking clarity, but I can't seem to shorten it, so bear with me...
A good case-study (my current case, of course) to illustrate the problem follows:
I write code for many locations, a Parent Compary (parentco), and several satellite locations (centers). I have two 'Managers', one designed for the parentco, and one designed for the centers (deployed many times). I also have two libraries, one for the centers, and one generic library (that is used at the centers and the parentco), that programs can include to communicate to the appropriate Manager (via TCP). The library for the centers has several classes designed to wrap database tables and other 'Messages' to do other things, and the generic library has a few 'Messages,' too, such as 'end connection,' 'invoke a process,' and others.
The Question:
When the Manager recieves a Message that is defined in the 'generic' library, how can it know which type of message it is? The first-blush solution would be something like this:
namespace generic_library
{
public interface IMessage_Creator
{
public IMessage Create_Message(short id);
}
public interface IMessage
{
short Message_ID { get; }
}
/// <summary>Perhaps a message to kill the current connection</summary>
public class Generic_Message1 : IMessage
{
public short Message_ID { get { return ID; } }
internal const short ID = 1;
}
public static class Message_Handler
{
private static readonly System.Collections.Generic.List<IMessage_Creator> _creators =
new System.Collections.Generic.List<IMessage_Creator>();
public static void Add_Creator(IMessage_Creator creator)
{
_creators.Add(creator);
}
public static IMessage Get_Message(short id)
{
switch (id)
{//the Generic library knows about the generic messages...
case Generic_Message1.ID:
return new Generic_Message1();
}
//no generic message found, search the registered creators.
IMessage ret = null;
foreach (IMessage_Creator creator in _creators)
{
ret = creator.Create_Message(id);
if (ret != null)
{
return ret;
}
}
//null if no creator was found.
return ret;
}
}
}
namespace center
{
public class Center_Creator : generic_library.IMessage_Creator
{
static Center_Creator()
{
generic_library.Message_Handler.Add_Creator(new Center_Creator());
}
public generic_library.IMessage Create_Message(short id)
{
switch (id)
{//The center library knows about center-specific messages
case center_message1.ID:
return new center_message1();
}
//we return null to say, "I don't know about that message id."
return null;
}
}
public class center_message1 : generic_library.IMessage
{
public short Message_ID
{
get { return ID; }
}
internal const short ID = 2;
}
}
A little explanation. As you can see, the center and generic library have their own messages they can handle. The center interface (here represented by namespace center
) registers his creator, Center_Creator
, in the static constructor so when the Message_Handler
gets a message of his type, the creator will be called on to generate the correct message.
The problem with this approach:
You may have already seen the problem here, and that is:
If the class Center_Creator is never accessed at all (one is never created, and a static method is never invoked) by code, which should be the case until a message of that type is recieved, the static constructor, static Center_Creator()
is never invoked, so the Message_Handler
never knows about this creator.
That's all fine and dandy, but I don't know how to fix it. Many people have suggested using reflection to invoke the Center_Creator
Type Initializer
, but I don't want to put that burden on every program that uses this library!
What is the Stack Overflow community's suggestion? Please let me know if I can simplify this to help make it more accessible for the community.
EDIT: The code is for the generic library and the Center Library. As you can see, I will have the same issues with the Parent Company library.