Reputation: 7321
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
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