How to call the correct method in Scala/Java based the types of two objects without using a switch statement?
Asked Answered
Q

5

9

I am currently developing a game in Scala where I have a number of entities (e.g. GunBattery, Squadron, EnemyShip, EnemyFighter) that all inherit from a GameEntity class. Game entities broadcast things of interest to the game world and one another via an Event/Message system. There are are a number of EventMesssages (EntityDied, FireAtPosition, HullBreach).

Currently, each entity has a receive(msg:EventMessage) as well as more specific receive methods for each message type it responds to (e.g. receive(msg:EntityDiedMessage) ). The general receive(msg:EventMessage) method is just a switch statement that calls the appropriate receive method based on the type of message.

As the game is in development, the list of entities and messages (and which entities will respond to which messages) is fluid. Ideally if I want a game entity to be able to receive a new message type, I just want to be able to code the logic for the response, not do that and have to update a match statement else where.

One thought I had would be to pull the receive methods out of the Game entity hierarchy and have a series of functions like def receive(e:EnemyShip,m:ExplosionMessage) and def receive(e:SpaceStation,m:ExplosionMessage) but this compounds the problem as now I need a match statement to cover both the message and game entity types.

This seems related to the concepts of Double and Multiple dispatch and perhaps the Visitor pattern but I am having some trouble wrapping my head around it. I am not looking for an OOP solution per se, however I would like to avoid reflection if possible.

EDIT

Doing some more research, I think what I am looking for is something like Clojure's defmulti.

You can do something like:

(defmulti receive :entity :msgType)

(defmethod receive :fighter :explosion [] "fighter handling explosion message")
(defmethod receive :player-ship :hullbreach []  "player ship handling hull breach")
Quorum answered 20/12, 2011 at 19:1 Comment(1)
If you want to add new types use inheritance. If you want to add new functionality use switch statements (pattern matching). This is the essential difference between objects and data structures.Fugger
C
9

You can easily implement multiple dispatch in Scala, although it doesn't have first-class support. With the simple implementation below, you can encode your example as follows:

object Receive extends MultiMethod[(Entity, Message), String]("")

Receive defImpl { case (_: Fighter, _: Explosion) => "fighter handling explosion message" }
Receive defImpl { case (_: PlayerShip, _: HullBreach) => "player ship handling hull breach" }

You can use your multi-method like any other function:

Receive(fighter, explosion) // returns "fighter handling explosion message"

Note that each multi-method implementation (i.e. defImpl call) must be contained in a top-level definition (a class/object/trait body), and it's up to you to ensure that the relevant defImpl calls occur before the method is used. This implementation has lots of other limitations and shortcomings, but I'll leave those as an exercise for the reader.

Implementation:

class MultiMethod[A, R](default: => R) {
  private var handlers: List[PartialFunction[A, R]] = Nil

  def apply(args: A): R = {
    handlers find {
      _.isDefinedAt(args)
    } map {
      _.apply(args)
    } getOrElse default
  }

  def defImpl(handler: PartialFunction[A, R]) = {
    handlers +:= handler
  }
}
Curtice answered 21/12, 2011 at 20:20 Comment(3)
This is very cool. One thing is that your defImpl in your usage example should be defmethod (or vice versa). I am going to give this a try and see how it goes. I will be curious to see how the performance scales when there are lots of messages being passed around. Thanks again.Quorum
regarding performance: please note that this solution at worst case enumerates each possible handler on each invocation. But this can be amended by using a hash based map instead of a list. Which of course would require to "tag" the handlers in a way that can be looked up by hash code. Scala-2.10 TypeTag comes to mindBlithe
You don't really need to elaborate that much. A simple partial function will do the job. If you want a dynamically generated aprtial function, then you can do val f = List(a,b).reduceLeft(_ orElse _).orElse(defaultValue)Ragan
W
3

If you're really worried about the effort it takes to create/maintain the switch statement, you could use metaprogramming to generate the switch statement by discovering all EventMessage types in your program. It's not ideal, but metaprogramming is generally one of the cleanest ways to introduce new constraints on your code; in this case that'd be the requirement that if an event type exists, there is a dispatcher for it, and a default (ignore?) handler that can be overridden.

If you don't want to go that route, you can make EventMessage a case class, which should allow the compiler to complain if you forget to handle a new message type in your switch statement. I wrote a game server that was used by ~1.5 million players, and used that kind of static typing to ensure that my dispatch was comprehensive, and it never caused an actual production bug.

Witten answered 20/12, 2011 at 19:57 Comment(2)
Thanks. With what I was doing I feel like I am violating DRY with having these extra match statements (one per game entity) in addition to the event handler itself. I haven't looked into metaprogramming in Scala, but I'll check it out.Quorum
It's really hard to not violate DRY in strong, statically typed languages. It's a good guideline but sufficiently interesting projects tend to force us to break our beloved rules of thumb.Witten
H
3

Chain of Responsibility

A standard mechanism for this (not scala-specific) is a chain of handlers. For example:

trait Handler[Msg] {
  handle(msg: Msg)
}

Then your entities just need to manage a list of handlers:

abstract class AbstractEntity {

    def handlers: List[Handler]

    def receive(msg: Msg) { handlers foreach handle }
}

Then your entities can declare the handlers inline, as follows:

class Tank {

   lazy val handlers = List(
     new Handler {
       def handle(msg: Msg) = msg match {
         case ied: IedMsg => //handle
         case _           => //no-op
       }
     },
     new Handler {
       def handle(msg: Msg) = msg match {
         case ef: EngineFailureMsg => //handle
         case _                    => //no-op
       }
     }
   )

Of course the disadvantage here is that you lose readability, and you still have to remember the boilerplate which is a no-op catch-all case for each handler.

Actors

Personally I would stick with the duplication. What you have at the moment looks a lot like treating each entity as if it is an Actor. For example:

class Tank extends Entity with Actor {

  def act() { 
    loop {
      react {
         case ied: IedMsg           => //handle
         case ied: EngineFailureMsg => //handle
         case _                     => //no-op
      }
    }
  }
}

At least here you get into the habit of adding a case statement within the react loop. This can call another method in your actor class which takes the appropriate action. Of course, the benefit of this is that you take advantage of the concurrency model provided by the actor paradigm. You end up with a loop which looks like this:

react {
   case ied: IedMsg           => _explosion(ied)
   case efm: EngineFailureMsg => _engineFailure(efm)
   case _                     => 
}

You might want to look at akka, which offers a more performant actor system with more configurable behaviour and more concurrency primitives (STM, agents, transactors etc)

Holocrine answered 20/12, 2011 at 21:43 Comment(1)
If I were going with the chain of responsibility approach I wonder if it would be easier to just embed the handlers in the case statement itself instead of having lots of receive(msg) methods. Thank you for the pointer to Akka, I'll be taking a look at that.Quorum
I
1

No matter what, you have to do some updating; the application won't just magically know which response action to do based off of the event message.

Cases are well and good, but as the list of messages your object responds to gets longer, so does its response time. Here is a way to respond to messages that will respond at the same speed no matter how many your register with it. The example does need to use the Class object, but no other reflections are used.

public class GameEntity {

HashMap<Class, ActionObject> registeredEvents;

public void receiveMessage(EventMessage message) {
    ActionObject action = registeredEvents.get(message.getClass());
    if (action != null) {
        action.performAction();
    }
    else {
        //Code for if the message type is not registered
    }
}

protected void registerEvent(EventMessage message, ActionObject action) {
    Class messageClass = message.getClass();
    registeredEventes.put(messageClass, action);
}

}

public class Ship extends GameEntity {

public Ship() {
    //Do these 3 lines of code for every message you want the class to register for. This example is for a ship getting hit.
    EventMessage getHitMessage = new GetHitMessage();
    ActionObject getHitAction = new GetHitAction();
    super.registerEvent(getHitMessage, getHitAction);
}

}

There are variations of this using Class.forName(fullPathName) and passing in the pathname strings instead of the objects themselves if you want.

Because the logic for performing an action is contained in the superclass, all you have to do to make a subclass is register what events it responds to and create an ActionObject that contains the logic for its response.

Illomened answered 20/12, 2011 at 20:8 Comment(0)
Q
1

I'd be tempted to elevate every message type into a method signature and Interface. How this translates into Scala I'm not totally sure, but this is the Java approach I would take.

Killable, KillListener, Breachable, Breachlistener and so on will surface the logic of your objects and commonalities between them in a way which permits runtime inspection (instanceof) as well as helping with runtime performance. Things which don't process Kill events won't be put in a java.util.List<KillListener> to be notified. You can then avoid the creation of multiple new concrete objects all the time (your EventMessages) as well as lots of switching code.

public interface KillListener{
    void notifyKill(Entity entity);
}

After all, a method in java is otherwise understood as a message - just use raw java syntax.

Quantity answered 20/12, 2011 at 20:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.