java – MessageHandler handles different message subtypes differently (can visitor pattern be applied?)

I’m working with Java 1.8. There is a lot of repetition in the MessageHandler class that I would like to remove but I’m having trouble implementing a fix.

External Code

I’m working with a message library that I should avoid changing. This library consists of these files:

BaseMessage.java

package external;

public abstract class BaseMessage {
    public void accept(MessageVisitor visitor) {
        visitor.visit(this);
    }
}

AMessage.java

package external;

public class AMessage extends BaseMessage {
}

BMessage.java

package external;

public class BMessage extends BaseMessage {
}

MessageVisitor.java

package external;

public interface MessageVisitor extends SendVisitor {
    default void visit(BaseMessage msg) {
        this.defaultAction(msg);
    }
}

SendVisitor.java

package external;

public interface SendVisitor {
    void defaultAction(BaseMessage var1);

    default void visit(AMessage msg) {
        this.defaultAction(msg);
    }

    default void visit(BMessage msg) {
        this.defaultAction(msg);
    }
}

Internal Code

Entrypoint.java

package example;

import external.AMessage;
import external.BMessage;
import external.BaseMessage;

class Entrypoint {
    MessageHandler handler;

    Entrypoint(MessageHandler handler) {
       this.handler = handler;
    }

    void handleMessage(BaseMessage message) {
        if(handler != null && handler.supports(message.getClass())) {
            if (handler.hasSeenBefore(message)) {
                // Log that we are skipping handling
                return;
            } else {
                handler.receive(message);
            }
        }

        // and do more unrelated things...
    }

    public static void main(String() args) {
        Entrypoint entrypoint = new Entrypoint(new MessageHandler());

        entrypoint.handleMessage(new AMessage());
        entrypoint.handleMessage(new BMessage());
    }
}

MessageHandler.java

package example;

import external.AMessage;
import external.BMessage;
import external.BaseMessage;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class MessageHandler {
    private static Set<Class<? extends BaseMessage>> SUPPORTED_MESSAGE_TYPES = new HashSet<>(
            Arrays.asList(AMessage.class, BMessage.class));

    public boolean supports(Class<? extends BaseMessage> messageType) {
        // this sucks
        return SUPPORTED_MESSAGE_TYPES.contains(messageType);
    }

    private boolean hasSeenBefore(AMessage message) {
        System.out.println("Checking if I've seen an AMessage");
        // more complicated in the actual code
        return true;
    }

    private boolean hasSeenBefore(BMessage message) {
        System.out.println("Checking if I've seen a BMessage");
        // more complicated in the actual code
        return false;
    }

    public boolean hasSeenBefore(BaseMessage message) {
        // TODO: make better
        if (message instanceof AMessage) {
            return hasSeenBefore((AMessage) message);
        } else if (message instanceof BMessage) {
            return hasSeenBefore((BMessage) message);
        } else {
            throw new UnsupportedOperationException("Tried to handle a message that is currently not supported: "
                    + message.toString());
        }
    }

    private void receive(AMessage message) {
        // do AMessage things
        System.out.println("Received an AMessage");
    }

    private void receive(BMessage message) {
        // do BMessage things
        System.out.println("Received a BMessage");
    }

    public void receive(BaseMessage message) {
        // TODO: make better
        if (message instanceof AMessage) {
            receive((AMessage) message);
        } else if (message instanceof BMessage) {
            receive((BMessage) message);
        } else {
            throw new UnsupportedOperationException("Tried to handle a message that is currently not supported: "
                    + message.toString());
        }
    }
}

Question

As you can see, it’s a pain to maintain the MessageHandler; specifically, the hasSeenBefore method has an instanceof that must be added to when another message is added, and a similar thing happens in the receive method. It is also really bad that the supports function keeps a separate list of those message types that must be updated when a new message type is added.

My hunch says that the Visitor pattern should be applied here more, but I’m having trouble implementing that.

  • How can I remove all this duplicated logic so that I could add new message types to the MessageHandler cleanly?
  • And how can supports check whether the message type is handled without maintaining a separate list?
  • Is there anything else in the internal code which is not following best practices?