2

So imagine we have a base class Message:

public abstract Class Message {

    Object content;

    public Message(Object content) {
        this.content = content;
    }
}

And various implementations:

public Class Packet extends Message {

    public Packet(Long largeNumber) {
        super(largeNumber);
    }

    public Long unpack() {
        return (Long) content;
    }
}

public Class Letter extends Message {

    public Letter(Short smallNumber) {
        super(smallNumber);
    }

    public Short unpack() {
        return (Short) content;
    }
}

Now suppose we have a sender class, that sends the Messages somewhere.

public Class Sender {

    public send(Message msg) {
        // send it somewhere
    }
}

And a receiver class, that receives the Message:

public Class Receiver {

    receive(Message msg) {
       // do something with the msg 
   }
}

The receiver class however just gets the Super class Message and doesn't know beforehand, which subclass it will receive. So how would I now "unpack" the message?

If we assume that I knew exactly what message would land where, I could use downcasting like this:

Packet packet = (Packet) msg;

But somehow this feels wrong as it kind of dismisses the point of polymorphism to begin with. Would it be better to just send the absolute sub-messages? Or is there a solution to such a problem I don't see (e.g. using Generics in some variation - I'm not too familiar with them)?

meow
  • 2,062
  • 2
  • 17
  • 27
  • You could overload receive with the different types of messages i.e. `receive(Packet pack)` `receive(Letter letter)` and do different things in each – Tyler Sep 08 '17 at 17:20
  • @Tyler That doesn't solve anything. Overloading is **compile time** based. You still need to do instanceof checks and downcasting. Pointless. – GhostCat Sep 08 '17 at 17:31
  • The answer really depends on what you mean by "do something with the msg". Show more specific example on how you want to implement `receive(Message msg)` method. – tsolakp Sep 08 '17 at 17:35
  • unpack it, nothing more really. – meow Sep 08 '17 at 17:44

4 Answers4

3

Unpacking the message can be done by the message itself, using the visitor pattern:

public abstract Class Message {
    void send(Receiver r) {
        r.receive(this); // Catch-all
    }
}

public Class Packet extends Message {
    void send(Receiver r) {
        r.receive(this); // Overload for packets
    }
}

public Class Letter extends Message {
    void send(Receiver r) {
        r.receive(this); // Overload for letters
    }
}

public Class Receiver {
    // There is an overload for each subclass
    receive(Packet packet) {
    }
    receive(Letter letter) {
    }
    // This is the catch-all implementation
    receive(Message msg) {
    }
}

This approach lets receiver process letters and packets separately, in a statically-typed context. Catch-all implementation is often used for error reporting.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Hm, thanks for the answer. I'm not quite sure how it really helps me though. In the end you it is just a nicer way of isinstanceof. Well, I'm not sure whether there even is a solution. Maybe that's just the way it is, like encrypting a message, the receiver has to know how to decrypt it. – meow Sep 08 '17 at 17:50
  • 1
    @meow No, this is absolutely not a "nicer way of instanceof". Unlike `instanceof`, this approach is statically type-safe, meaning that the decision of which of the methods of the `Receiver` to call is made at compile time, not at run-time. This is *the* solution for statically typed languages. – Sergey Kalinichenko Sep 08 '17 at 18:14
  • Ye I do understand now, what you mean. I'll play a bit with the idea. Always disliked the visitor pattern, guess I have to embrace it now :S – meow Sep 08 '17 at 18:18
1

The way to go is generics:

public abstract class Message<T> {

    private T content;

    public T unpack() {
        return content;
    }

}
Mordechai
  • 15,437
  • 2
  • 41
  • 82
0

If you want to use polimorfism properties, you must to declare abstract method unpack in parent class Message and then override it in a derivative class:

    public abstract class Message {
     public abstract String unpack();
     // other code
}
public class Letter extends Message {
    public String unpack() {
        // your unpacked code
    }
}
public class Receiver{
    receive(Message msg){
        String s = msg.unpack(); 
        /* you can invoke unpack method because earlier declared it as abstract in parent class */
    }
}
  • That's not really the clue, of course this is a simplification, in reality Message would define some abstractions to be implemented. The core of the problem is how to unpack the super type Message. – meow Sep 08 '17 at 17:52
0

I would suggest create a map of handlers for each type of Message. The content of the message should not lose its type. An examples of Message and Letter:

abstract class Message<T> {
    private T content;
    protected Message(T content) {
        this.content = content;
    }
    public T getContent() {
        return content;
    }
}

class Letter extends Message<Short> {
    public Letter(Short number) {
        super(number);
    }
}

Then create an interface for handlers:

interface MessageHandler<T extends Message> {
    void handle(T message);
}

Then create some "provider" to store all handlers:

class MessageHandlersProvider {
    Map<Class<? extends Message>, MessageHandler<? extends Message>> handlers = new HashMap<>();
    <T extends Message> void addHandler(Class<T> messageType, MessageHandler<T> handler) {
        handlers.put(messageType, handler);
    }

    MessageHandler<?> getHandler(Class<? extends Message> messageType) {
        return handlers.get(messageType);
    }
}

The example of a handler:

class LetterHandler implements MessageHandler<Letter> {
    @Override
    public void handle(Letter letter) {
        //Short number = letter.getContent();
        //do anything
    }
}

Now you can create a provider and fill it by your handlers this way:

MessageHandlersProvider handlersProvider = new MessageHandlersProvider();
handlersProvider.addHandler(Letter.class, new LetterHandler());

Then get a handler and handle any type of Message:

MessageHandler<?> handler = handlersProvider.getHandler(message.getClass());
handler.handle(message);

It is really important to fill the map through the generified method addHandler. It ensures that you did not add a handler for a wrong type of Message.

I know it breaks generics at the last line, but it seems pretty safe as we guarantee that a wrong handler will not be put and taken then.

OutOfNPE
  • 286
  • 3
  • 5