如何在Scala/Java中调用基于两个对象类型的正确方法,而不使用switch语句?

如何在Scala/Java中调用基于两个对象类型的正确方法,而不使用switch语句?,scala,double-dispatch,Scala,Double Dispatch,我目前正在Scala中开发一个游戏,其中我有许多实体(例如炮兵队、中队、敌人、敌人战斗机),它们都是从GameEntity类继承的。游戏实体通过事件/消息系统向游戏世界和彼此广播感兴趣的内容。有许多事件消息(EntityDied、FireAtPosition、HullBreach) 目前,每个实体都有一个receive(msg:EventMessage),以及针对其响应的每种消息类型的更具体的接收方法(例如receive(msg:EntityDiedMessage))。常规的receive(ms

我目前正在Scala中开发一个游戏,其中我有许多实体(例如炮兵队、中队、敌人、敌人战斗机),它们都是从GameEntity类继承的。游戏实体通过事件/消息系统向游戏世界和彼此广播感兴趣的内容。有许多事件消息(EntityDied、FireAtPosition、HullBreach)

目前,每个实体都有一个
receive(msg:EventMessage)
,以及针对其响应的每种消息类型的更具体的接收方法(例如
receive(msg:EntityDiedMessage)
)。常规的
receive(msg:EventMessage)
方法只是一个switch语句,它根据消息的类型调用适当的receive方法

随着游戏的开发,实体和消息(以及哪些实体将响应哪些消息)的列表是流动的。理想情况下,如果我希望游戏实体能够接收新的消息类型,我只希望能够为响应编码逻辑,而不是这样做,并且必须在其他地方更新匹配语句

我的一个想法是将receive方法从游戏实体层次结构中拉出来,并具有一系列功能,如
def receive(e:EnemyShip,m:ExplosionMessage)
和def
receive(e:SpaceStation,m:ExplosionMessage)
但这加剧了问题,因为现在我需要一个匹配语句来涵盖消息和游戏实体类型

这似乎与调度的概念有关,也许还与访问者模式有关,但我很难理解它。我不是在寻找OOP解决方案本身,但是如果可能的话,我希望避免反射

编辑

做更多的研究,我想我想要的是Clojure的
defmulti

您可以执行以下操作:

(defmulti receive :entity :msgType)

(defmethod receive :fighter :explosion [] "fighter handling explosion message")
(defmethod receive :player-ship :hullbreach []  "player ship handling hull breach")

如果您确实担心创建/维护switch语句所需的工作量,那么可以使用元编程通过发现程序中的所有EventMessage类型来生成switch语句。这并不理想,但元编程通常是在代码中引入新约束的最干净的方法之一;在这种情况下,如果存在一个事件类型,则需要有一个事件类型的调度程序,以及一个可以重写的默认(ignore?)处理程序


如果您不想这样做,可以将EventMessage设置为case类,这样当您忘记在switch语句中处理新的消息类型时,编译器就会抱怨。我编写了一个大约150万玩家使用的游戏服务器,并使用这种静态类型来确保我的调度是全面的,并且它不会导致实际的生产错误。

不管怎样,你必须做一些更新;应用程序不会根据事件消息神奇地知道要执行哪个响应操作

案例很好,但随着对象响应的消息列表变长,其响应时间也变长。这是一种响应消息的方法,无论您注册了多少条消息,这些消息都会以相同的速度响应。该示例确实需要使用类对象,但不使用其他反射

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);
}
}

使用Class.forName(fullPathName)和传入路径名字符串(而不是对象本身,如果需要的话)有多种不同的方法


因为执行操作的逻辑包含在超类中,所以创建子类所需做的就是注册它响应的事件,并创建一个包含其响应逻辑的ActionObject。

我会尝试将每个消息类型提升为方法签名和接口。我不完全确定这是如何转化为Scala的,但这是我将采用的Java方法

Killable、KillListener、breakable、breaklistener等将以一种允许运行时检查(instanceof)并有助于提高运行时性能的方式呈现对象的逻辑和它们之间的共性。不处理Kill事件的东西不会被放入
java.util.List
中以得到通知。然后,您可以避免始终创建多个新的具体对象(EventMessages)以及大量切换代码

public interface KillListener{
    void notifyKill(Entity entity);
}
毕竟,java中的方法在其他方面被理解为消息——只需使用原始java语法。

责任链 这方面的标准机制(不是scala特有的)是处理程序链。例如:

trait Handler[Msg] {
  handle(msg: Msg)
}
class Tank extends Entity with Actor {

  def act() { 
    loop {
      react {
         case ied: IedMsg           => //handle
         case ied: EngineFailureMsg => //handle
         case _                     => //no-op
      }
    }
  }
}
然后,您的实体只需要管理处理程序列表:

abstract class AbstractEntity {

    def handlers: List[Handler]

    def receive(msg: Msg) { handlers foreach handle }
}
然后您的实体可以内联声明处理程序,如下所示:

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
       }
     }
   )
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" }
当然,这里的缺点是您失去了可读性,并且您仍然必须记住样板文件,这对于每个处理程序来说都是一个不可操作的包罗万象的例子

演员 就我个人而言,我会坚持复制。您现在拥有的看起来非常像是将每个实体视为一个
参与者。例如:

trait Handler[Msg] {
  handle(msg: Msg)
}
class Tank extends Entity with Actor {

  def act() { 
    loop {
      react {
         case ied: IedMsg           => //handle
         case ied: EngineFailureMsg => //handle
         case _                     => //no-op
      }
    }
  }
}
至少在这里,您养成了在react循环中添加
case
语句的习惯。这可以调用actor类中采取适当操作的另一个方法。当然,这样做的好处是利用了actor范式提供的并发模型。您将得到一个如下所示的循环:

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

您可能想看看,它提供了一个性能更高的参与者系统,具有更可配置的行为和更多并发原语(STM、代理、事务处理程序等)

您可以轻松地在Scala中实现多个分派,尽管它没有一流的支持。通过下面的简单实现,您可以对示例进行如下编码:

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
       }
     }
   )
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" }
您可以像使用任何其他函数一样使用多重方法:

Receive(fighter, explosion) // returns "fighter handling explosion message"
请注意,每个多方法实现(即
definpl
调用)必须包含在顶级定义(类/对象)中