Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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")
like image 322
Michael Avatar asked Dec 20 '11 19:12

Michael


People also ask

When we are writing a Scala program we always start with object instead of using a class like Java What is that type of object is?

Instead, Scala has singleton objects. A singleton is a class that can have only one instance, i.e., Object. You create singleton using the keyword object instead of class keyword.

What does ::: mean in Scala?

:: - adds an element at the beginning of a list and returns a list with the added element. ::: - concatenates two lists and returns the concatenated list.


1 Answers

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
  }
}
like image 51
Aaron Novstrup Avatar answered Oct 26 '22 22:10

Aaron Novstrup