Johnny
Johnny

Reputation: 7321

Conditional to polymorphism - a more complicated case

Say I have this well known example:

   double getSpeed() {
       switch (_type) {
           case EUROPEAN:
              return getBaseSpeed();
           case AFRICAN:
              return getBaseSpeed() - getLoadFactor() * _numberOfCoconuts;
           case NORWEGIAN_BLUE:
              return (_isNailed) ? 0 : getBaseSpeed(_voltage);
       }
       throw new RuntimeException ("Should be unreachable");
   }

Obviously, I'd refactor into subclasses and everything will be made right in the world again. But what if I have:

   double getSpeed() {
       switch (_type) {
           case EUROPEAN:
              inform_gary(_count);  
              return getBaseSpeed();
           case AFRICAN:
              increment_package_counter();
              transmit_coordinates(_coordinates);
              return getBaseSpeed() - getLoadFactor() * _numberOfCoconuts;
           case NORWEGIAN_BLUE:              
              return (_isNailed) ? 0 : getBaseSpeed(_voltage);
       }
       throw new RuntimeException ("Should be unreachable");
   }

Now it would be useless to subclass because then I'd have to tightly couple the subclasses to code they shouldn't know about. Is there a solution to this problem?

Upvotes: 0

Views: 66

Answers (1)

sdgfsdh
sdgfsdh

Reputation: 37045

The question's comments refine the question a bit; I'll answer the updated version.


I would make a class for each possible message type. These would inherit a message interface with an execute function.

Often, a message will need to perform some actions which require references to the execution environment. Since life is much easier when message objects are immutable, I would pass these references as a parameter to the execute function.

The interface:

interface Message
{
    void execute(Context context)
}

And an example message:

class RebroadcastMessage : Message
{
    private final String content;
    private final int timeToLive;

    public RebroadcastMessage(String content, int timeToLive) {

        this.content = content;
        this.timeToLive = timeToLive;
    }

    void execute(Context context)
    {
        if (timeToLive > 0)
        {
            foreach (Peer p in context.Peers)
            {
                context.send(p, new RebroadcastMessage(content, timeToLive - 1));
            }
        }
    }
}

This makes processing messages easy:

messageQueue.take().execute(this.context); 

Based on the discussion below, I will explain another technique.

Sometimes you want to handle state between messages, but do not want to put this logic in the message receiving class. In these cases, I would suggest an event-driven design.

A Session takes messages (e.g. from a network connection) and updates all listeners for each message received.

interface MessageListener 
{
    void handle(Context context, Message message);
}

interface Session
{
    void addListener(MessageListener listener);
}

You can then implement separate a MessageListener for each bit of functionality you require.

class FruitTracker implements MessageListener 
{
    private Set<Fruit> stock;

    public FruitTracker()
    {
        stock = new HashSet<>();
    }

    void handle(Context context, Message message)
    {
        if (message instanceof StockQueryMessage)
        {
            context.send(context.getSender(), new StockQuoteMessage(stock));
        } 
        else if (message instanceof StockUpdateMessage)
        {
            stock = ((StockUpdate)message).getStock();
        }
    }
}

Set up:

val fruitTracker = new FruitTracker();

session.addListener(fruitTracker);

Upvotes: 1

Related Questions