Best table / enum driven method calling system
Consider I'm interfacing with an external system that will send a message (DB table, message queue, web service) in some format. In the "message header" there is the "MessageType" that is a number from 1 to 20. The MessageType defines what to do with the rest of the message. There are things like new, modified, deleted, canceled...
My first inclination was to setup an enumeration and define all the types. Then parse the number into an enum type. With it as an enum I would setup the typical switch case system and call a particular method for each of the message types.
One big concern is maintenance.
A switch / case system is bulky and teadious but, it's really simple.
Various table / configuration systems can be difficult for someone else to grok and add new messages or tweak existing messages.
For 12 or so MessageTypes the switch/case system seems quite reasonable. What would be a reasonable cut-off point to switch to a table driven system?
What kinds of systems are considered best for handling these types of problems?
I'm setting a tag for both C# and Java here because it's definitly a common problem. There are many other languages with the same issue.
Asked by: Sawyer145 | Posted: 21-01-2022
Answer 1
In Java, you can make it an enum and give behaviour to the different values (although with 100 values, I'd hope that each type of behaviour is briefly, calling out to "proper" classes).
In C#, you can have a map from value to some appropriate delegate type - then when you statically construct the map, you can either use lambda expressions or method group conversions as appropriate.
Having said that, setting up the map is going to be just as ugly as a switch statement. If each switch statement is just a single method call, you might like to try this sort of format:
switch (messageType)
{
case 0: HandleLogin(message); break;
case 50: SaveCurrentDocument(message); break;
case 100: HandleLogout(message); break;
}
(etc). I know it's against normal conventions, but it can be quite neat for the odd exceptional situation like this. If you only need the numbers in one place, then there's little point in introducing constants - basically the line containing the number effectively is the constant definition!
Answered by: Brianna846 | Posted: 22-02-2022Answer 2
How about having a Dictionary<MessageType, ProcessMessageDelegate>
to store those methods by their message types? During initialization of the class, register all the methods in this dictionary. Then call the appropriate method. Following is the pseudo code:
delegate void ProcessMessageDelegate(Message message)
public class MyMessageProcessor
{
Dictionary<int, ProcessMessageDelegate> methods;
public void Register( int messageType,
ProcessMessageDelegate processMessage)
{
methods[messageType] = processMessage;
}
public void ProcessMessage(int messageType, Message message)
{
if(methods.ContainsKey(messageType))
{
methods[messageType](message);
}
}
}
To register methods:
myProcessor.Register(0, ProcessMessageOfType0);
myProcessor.Register(1, ProcessMessageOfType1);
myProcessor.Register(2, ProcessMessageOfType2);
...
Edit: I realized Jon already suggests having a map which now makes my answer redundant. But I don't understand why a statically constructed map is uglier than switch case?
Answered by: Roman111 | Posted: 22-02-2022Answer 3
Have a handler interface like this:
interface MessageHandler {
void processMessage(Message msg) throws Exception;
int[] queryInterestingMessageIds();
int queryPriority(int messageId); // this one is optional
}
Locate, instantiate and register your handlers. You might want to use some reflection-based mechanism like ServiceLoader, Spring (explicit config or classpath scanning amd possibly autowiring) or plain properties file
The registration should pass each handler to a WhateverManager class which would hold internally a map (or arry indexed by message ID) of collections of handlers. If you expect to have multiple handlers, you can use the queryPriority(int) method to resolve the order of handling (otherwise you can treat it as an error and throw an exception at config time). It's a good practice to NOT use static map for registration.
If you decide to support multiple handlers for a message, you might want to daisychain them. In such case one way is to change the signature as follows:
Message processMessage(Message msg, Message original) throws Exception;
Answered by: Sawyer291 | Posted: 22-02-2022
Answer 4
This is how I've done this in C#.
I think this approach is actually not so ugly as all that, It gets less ugly as the number of message types increases: to implement a new message type, you just have to add a value to your Enum and mark the new message handler class with an attribute.
And there are circumstances where being able to load your message handlers from an assembly at runtime is a very powerful feature; you can have a single executable that behaves differently based on which message-handler assembly is installed.
Start by creating an interface for the message handler (we'll call it IMessageHandler
) and an Enum for the message type (we'll call it MessageType
).
Next, create a class called MessageHandlerAttribute
:
public class MessageHandlerAttribute : System.Attribute
{
public MessageType MessageType { get; set; }
}
Now implement each message handler as a separate class, and mark each class with its message type attribute. If a message handler can handle multiple types of message, you can put multiple attributes on it:
[MessageHandler(MessageType=MessageType.Login)]
public class LoginMessageHandler : IMessageHandler
{
...
}
It's important that these message handlers all have parameterless constructors. I can't think of a good reason that you'd want a message handler's constructor to take parameters, but if any does, the code below can't handle it.
Build all the message handlers into the same assembly, and be sure you have a way to know its path at runtime. (That's the big point of failure for this approach.)
Now we can use Reflection to build a map of message handlers at runtime:
using System.Reflection;
...
Assembly mhAssembly = Assembly.LoadFrom(mhAssemblyPath);
Dictionary<MessageType, IMessageHandler> mhMap = new Dictionary<MessageType, IMessageHandler>();
foreach (Type t in mhAssembly.GetExportedTypes())
{
if (t.GetInterface("IMessageHandler") != null)
{
MessageHandlerAttribute list = (MessageHandlerAttribute[])t.GetCustomAttributes(
typeof(MessageHandlerAttribute), false);
foreach (MessageHandlerAttribute att in list)
{
MessageType mt = att.MessageType;
Debug.Assert(!mhMap.ContainsKey(mt));
IMessageHandler mh = mhAssembly.CreateInstance(
t.FullName,
true,
BindingFlags.CreateInstance,
null,
new object[] { },
null,
null);
mhMap.Add(mt, mh);
}
}
// depending on your application, you might want to check mhMap now to make
// sure that every MessageType value is in it.
}
return mhMap;
Now when you get a message, you can handle it like this:
Debug.Assert(MhMap.ContainsKey(Message.MessageType));
IMessageHandler mh = MhMap[Message.MessageType];
mh.HandleMessage(Message);
This code's all based on code I have in a production system right now; I've changed it slightly (so that the message handlers implement an interface instead of deriving from an abstract class, and that it handles multiple message handler attributes), which has probably introduced bugs into it.
Answered by: Brianna298 | Posted: 22-02-2022Similar questions
java - Is it possible to get calling page name inside a jsp 2.0 custom tag?
I'm writing a custom JSP tag using the JSP 2 tag files. Inside my tag I would like to know which page called the tag in order to construct URLs. Is this possible with out passing it through an attribute?
Calling Java from Clojure
When I try to run the following code (from the REPL) in Clojure:
(dotimes [i 5]
(.start
(Thread.
(fn []
(Thread/sleep (rand 1000))
(println (format "Finished %d on %s" i (Thread/currentThread)))))))
I get the following error:
java.lang.Exception: Unable to resolve symbol: i in this context
clojure.lang.Compiler$CompilerException: NO_SOURCE_FILE:6: Unabl...
Tail calling in Java and C#?
I was reading about Clojure and found a discussion about Java not supporting tail calls, in the current version, and that people were throwing exceptions to simulate tail calling in the JVM, anyways it sounds like people are doing some crazy stuff out there. So this got me wondering about C#'s tail calling, same issues?
java - Calling a Remote Bean vs Local Bean in App Server
Is there a noticeable amount of performance overhead in using Remote Bean Interface over using a Local Bean Interface? I would like to have every Client application connect to remote beans if there is little performance difference.
java - Calling a global Array
I'm currently trying to draw shapes with 2D Arrays. In my class there is a global array defined with public char canvas[][];
Up until now, I have only declared arrays with char canvas[][] = new char[height][width];
If this Array has already been declared, and I'm not supposed to amend the code I've been given, how do I call an instance of that array so that I can use it?
th...
java - Calling servlet from another servlet
I have two servlets which are running on different tomcat servers.
I and trying to call a servlet1 from servlet2 in the following way and wanted to write an object to output stream.
URL url=new URL("http://msyserver/abc/servlet1");
URLConnection con=url.openConnection();
con.setDoOutput(true);
con.setDoInput(true);
OutputStream os=con.getOutputStream();
ObjectOutputStream oos=new ObjectOutputStream...
java - How do I find out the calling class?
how can i find out which class/method has called the actual method?
Unkown error when calling Java applet from JavaScript
Here's the JavaScript (on an aspx page):
function WriteDocument(clientRef, system, branch, category, pdfXML)
{
AppletReturnValue = document.DocApplet.WriteDocument(clientRef, apmBROOMS, branch, category, pdfXML);
if (AppletReturnValue.length > 0) {
document.getElementById('pdfData').value = "";
CallServer(AppletReturnValue,'');
}
PostBackAndDisplayPDF()
}
java - calling an exe file inside jar
I am trying to call the "dspdf.exe" inside the jar file where this smartpdf class exists. I plan to extract it to a temp location and delete when program ends. However this doesn't seem to work, any help will be appreciated.
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.omg.CORBA.portable.InputStream;
public class smartpdf {
static String url="";
static St...
c# client calling java axis2 web service, object "resets"
I am very new to web service stuff so please be kind.
I have written a simple POJO class, and deployed it on an axis2 server:
public class Database {
private Project project;
public void login(){
project = new Project();
project.setDescription("Hello there");
project.setName("To me");
}
public Project getProject(){
return project;
}
}
Still can't find your answer? Check out these amazing Java communities for help...
Java Reddit Community | Java Help Reddit Community | Dev.to Java Community | Java Discord | Java Programmers (Facebook) | Java developers (Facebook)