Design Pattern for Handling Multiple Message Types

Design pattern for handling multiple message types

You could create separate message handlers for each message type, and naively pass the message to each available handler until you find one that can handle it. Similar to the chain of responsibility pattern:

public interface IMessageHandler {
bool HandleMessage( IMessage msg );
}

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

... etc ...

public class MessageProcessor {
private List<IMessageHandler> handlers;

public MessageProcessor() {
handlers = new List<IMessageHandler>();
handlers.add(new SomeOtherMessageHandler());
handlers.add(new OrderMessageHandler());
}

public void ProcessMessage( IMessage msg ) {
bool messageWasHandled
foreach( IMessageHandler handler in handlers ) {
if ( handler.HandleMessage(msg) ) {
messageWasHandled = true;
break;
}
}

if ( !messageWasHandled ) {
// Do some default processing, throw error, whatever.
}
}
}

You could also implement this as a map, with the message class name or message type id as a key and the appropriate handler instance as the value.

Others have suggested having the message object "handle" itself, but that just doesn't feel right to me. Seems like it would be best to separate the handling of the message from the message itself.

Some other things I like about it:

  1. You can inject the message handlers via spring or what-have-you rather than creating them in the constructor, making this very testable.

  2. You can introduce topic-like behavior where you have multiple handlers for a single message by simply removing the "break" from the ProcessMessage loop.

  3. By separating the message from the handler, you can have different handlers for the same message at different destinations (e.g. multiple MessageProcessor classes that handle the same messages differently)

Design pattern to process different messages based on message type

You can create a map with message type as key and processing logic as value

public static Map<String, Processor> PROCESSORS_MAP = Map.of(
"catalog_purchased", customMsg -> System.out.println("catalog_purchased: " + customMsg),
"catalog_expired", customMsg -> System.out.println("catalog_expired: " + customMsg),
"catalog_denied", customMsg -> System.out.println("catalog_denied: " + customMsg)
);

where Processor is a functional interface

@FunctionalInterface
interface Processor {
void process(CustomMessage msg);
}

and use it as follows

PROCESSORS_MAP.get(customMessage.getMessageType()).process(customMessage);

Of course if logic for processing a message is pretty big you should create a class that implements Processor instead of using lambda.

Design pattern for loading multiple message types

The next level of abstraction is to make Message discovery and instantiation dynamic. This is often accomplished by associating a string name with each Message or by using the name of the class as an identifier. You can use Reflection to discover available Message types, store them in a Dictionary and provide instantiation by name. This can be further extended to bring in Messages from dynamically loaded 'plugin' assemblies, with appropriate meta-data and interfaces to allow for loosely coupled composition between different Messages and Message Consumers. Once you get to that level, I recommend looking into frameworks like MEF which automate the discovery and instance injection process.

For your simple application, I think your approach is already quite clean. A series of if statements or a switch works just fine and is easy to understand/maintain, as long as you have a relatively small and stable set of cases.


Summarizing the further discussion in the comments:

The main design concern creating uneasiness was the fact that the different specific messages inherited from Message and yet a base Message had to be instantiated before the more specific messages could perform further analysis. This muddied up whether the Message is intended to contain raw information or to act as a base type for interpreted messages. A better design is to separate the RawMessage functionality into its own class, clearly separating concerns and resolving the feeling of 'instantiating twice'.

As for refactoring with DTOs and a mapper class:

I actually prefer your approach for an app-specific message encoding/decoding. If I want to track down why FactoryTakenOverByRobotsMessage contains invalid data, it makes sense to me that the parser method for the message is contained with the decoded data for the message. Where things get more dicey if when you want to support different encodings, as now you start wanting to specify the DTO declaratively (such as with attributes) and allow your different transport layers to decide how to serialize/deserialize. In most cases where I'm using your pattern, however, it's for a very app-specific case, with often somewhat inconsistent message formats and various proprietary encodings that don't map well in any automatic way. I can always still use the declarative encoding in parallel with the proprietary, in-class encoding and do things like serialize my messages to XML for debugging purposes.

Design Pattern for handling different types of instructions to make different types of method calls

I agree with Kayaman in regards to the Command pattern design. Nonetheless, I think it's good that you know there's another way as well, through Java's Reflection API. Assuming that what you want is to be able to do the following:

evaluate(instruction, call);

Without having to have in the method evaluate(...) a whole bunch of switches or if elses. I'd suggest using Java's API reflection, meaning that you'd be able to add as many instructions and calls as you want without having to change the code that evaluates them (functionality) (the core reason for object oriented programming).

Hope I don't lose you in the following explanation. Let's start with declaring the Instruction and Call abstract classes.

public abstract class Call {
String name;

public Call(String name){
this.name=name;
}

public String evaluate(){
return this.name;
}

}

public abstract class Instruction {
String name;

public Instruction(String name){
this.name=name;
}

public String evaluate(){
return this.name;
}
}

I've placed very basic functionality just to see that it works. Now let's create a couple of different classes that extend from Instruction and Call, so that we can have something to play with.

public class Instruction1 extends Instruction {

public Instruction1(String name) {
super(name);
}

}

public class Call1 extends Call {

public Call1(String name) {
super(name);
}

}

I'm not going to post all the classes because it's got the same code as the Instruction1 and Call1. So the classes I created were: Instruction1, Instruction2, Instruction3,Call1, Call2, Call3.

Now that we have that, we're going to create the class that is in charge of handling the correct call of the combination (instruction, call). Your evaluator, if you will.

public class InstructionCallsEvaluator {
public String evaluate(Instruction1 instruction, Call1 call){
return this.evaluate( (Instruction) instruction, (Call) call);
}

public String evaluate(Instruction1 instruction, Call2 call){
return this.evaluate( (Instruction) instruction, (Call) call);
}

public String evaluate(Instruction1 instruction, Call3 call){
return this.evaluate( (Instruction) instruction, (Call) call);
}

public String evaluate(Instruction2 instruction, Call1 call){
return this.evaluate( (Instruction) instruction, (Call) call);
}

public String evaluate(Instruction2 instruction, Call2 call){
return this.evaluate( (Instruction) instruction, (Call) call);
}

public String evaluate(Instruction2 instruction, Call3 call){
return this.evaluate( (Instruction) instruction, (Call) call);
}

public String evaluate(Instruction3 instruction, Call1 call){
return this.evaluate( (Instruction) instruction, (Call) call);
}

public String evaluate(Instruction3 instruction, Call2 call){
return this.evaluate( (Instruction) instruction, (Call) call);
}

public String evaluate(Instruction3 instruction, Call3 call){
return this.evaluate( (Instruction) instruction, (Call) call);
}

private String evaluate(Instruction instruction, Call call){
return instruction.evaluate() + " " + call.evaluate();
}
}

Quite long eh? Here is where you'd be adding all the combinations possible. So if you were to add a new Class# , you'd have to come to this evaluator and add the corresponding combination. You could view this as a downside if compared to the Command pattern implementation.

The reason why I added the private String evaluate(Instruction instruction, Call call) is because if formatting of output changed, I'd only have to change this method, and not all the other methods.

Also, the reason why I upcast the method parameters before calling this method is because otherwise you'll get a never ending loop resulting in a stackOverflow Exception:

public String evaluate(Instruction3 instruction, Call1 call){
return this.evaluate(instruction, call);
}

If I were to leave it like this and execute, when it calls the this.evaluate(...) it'll end up recalling public String evaluate(Instruction3 instruction, Call1 call) in a never ending loop.

The reason why I am showing this way of implementation is because I assume for some reason you want to identify what kind of combination Instruction, call makes. Otherwise, I'd go with the Command Pattern.

Obviously, what I do inside the evaluate(...)** is very simple because I don't know exactly what you are trying to do. You'd have to adapt this to your situation.

Now that the handler is set up, let's go for the class that is in charge of calling it properly without if elses and switches. Your Handler, if you will.

public class InstructionHandler {

public String evaluate(Instruction instruction, Call call) {
try {
return useEvaluator(InstructionCallsEvaluator.class, instruction, call);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException | NoSuchMethodException ex) {
Logger.getLogger(InstructionHandler.class.getName()).log(Level.SEVERE, null, ex);
}
return "Something went wrong when calling Evaluator";
}

private String useEvaluator(Class<InstructionCallsEvaluator> instructionCallsEvalutor, Instruction instruction, Call call)
throws InstantiationException, IllegalAccessException, IllegalArgumentException,
InvocationTargetException, NoSuchMethodException {
Class[] methodParameterTypes = new Class[]{instruction.getClass(), call.getClass()};
Object instance = instructionCallsEvalutor.newInstance();
Method method = instructionCallsEvalutor.getDeclaredMethod("evaluate", methodParameterTypes);
return method.invoke(instance, instruction, call).toString();
}

}

Let's go through this step by step. All calls will come to

`public String evaluate(Instruction instruction, Call call)`

which has the abstract classes as parameters. Once in there, it has to call the appropriate evaluate(...) method of the class InstructionCallsEvaluator. The reason why I separated it in two methods is to have the try{} catch{} in one and the actual method in the other (cleanliness and readability).

You already know the class whose methods you want to invoke:
InstructionCallsEvaluator, but you don't know which method you are going to invoke. That's where

private String useEvaluator(Class<InstructionCallsEvaluator> instructionCallsEvalutor, Instruction instruction, Call call)

comes in.

This method handles the methodParameterTypes, creates an instance of your InstructionCallsEvaluator class, and determines the method that applies to the parameters passed. Once it has got that method, it simply invokes it. Let's try out with this main class:

public class Main {

public static void main(String[] args) {
ArrayList<Instruction> instructions = getInstructions();
ArrayList<Call> calls = getCalls();
InstructionHandler handler = new InstructionHandler();

for(Instruction instrct : instructions)
for(Call call : calls)
System.out.println(handler.evaluate(instrct, call));
}

private static ArrayList<Instruction> getInstructions(){
ArrayList<Instruction> instructions = new ArrayList<>();
instructions.add(new Instruction1("Instruction 1"));
instructions.add(new Instruction2("Instruction 2"));
instructions.add(new Instruction3("Instruction 3"));
return instructions;
}

private static ArrayList<Call> getCalls(){
ArrayList<Call> calls = new ArrayList<>();
calls.add(new Call1("Call 1"));
calls.add(new Call2("Call 2"));
calls.add(new Call3("Call 3"));
return calls;
}

This is the output:

Instruction 1  Call 1
Instruction 1 Call 2
Instruction 1 Call 3
Instruction 2 Call 1
Instruction 2 Call 2
Instruction 2 Call 3
Instruction 3 Call 1
Instruction 3 Call 2
Instruction 3 Call 3

You might be wondering why didn't I just skip the whole InstructionHandler class altogether and went for:

InstructionCallsEvaluator evaluator = new InstructionCallsEvaluator();
evaluator.evalute(....);

That's because then I'd have to manually call evaluator.evalute(....); with each proper Class and Instruction class, so that my main would look something like this:

   evaluator.evalute(new Instruction1(), new Call1());
evaluator.evalute(new Instruction2(), new Call2());
...

Rather than being able to call it passing it the upcast of said classes. Of course, this depends on what your goal is, you might just want this, and in that case, you can skip the InstructionHandler class altogether.

If you were not to do the evaluator.evalute(new Instruction1(), new Call1()); and just call it with the upcast, then you'd get a compile time error. You'd have to change the access modifier from

    private String evaluate(Instruction instruction, Call call){
return instruction.evaluate() + " " + call.evaluate();
}

to public. But then it would always go through that method, and you'd lose the possibility of knowing what combination of (Instruction, Call) occurred.

Once again, this answer assumes your objective, since I had a bit of a hard time understanding what you want to do. Hope it helps either you, or anyone looking into this question.



Related Topics



Leave a reply



Submit