2

我正在 Scala 中实现一个 GUI 事件系统。我有类似的东西:

case class EventObject
case class KeyEventObject extends EventObject
case class MouseEventObject extends EventObject

我想将事件侦听器闭包存储在(多)映射中,如下所示:

var eventListeners = new MultiMap[EventDescriptor, (EventObject => Unit)];

我的问题是,有没有办法重写它,以便存储闭包的函数签名可以是 EventObject 或任何子类?类似于以下内容:

var eventListeners = new MultiMap[EventDescriptor, [A <: EventObject](A => Unit)]

这样我就可以在定义侦听器函数时知道子类型:

eventListeners.put(KEY_EVENT, (e:KeyEventObject) => { ... })
eventListeners.put(MOUSE_EVENT, (e:MouseEventObject) => { ... })
4

3 回答 3

2

并不是很多事情是不可能的。例如,您可以使用类型类执行以下操作:

class HMultiMap {
  import scala.collection.mutable.{ Buffer, HashMap }

  type Mapping[K, V]

  private[this] val underlying = new HashMap[Any, Buffer[Any]]

  def apply[K, V](key: K)(implicit ev: Mapping[K, V]) =
    underlying.getOrElse(key, Buffer.empty).toList.asInstanceOf[List[V]]

  def add[K, V](key: K)(v: V)(implicit ev: Mapping[K, V]) = {
    underlying.getOrElseUpdate(key, Buffer.empty) += v
    this
  }
}

现在:

sealed trait EventObject
case class KeyEventObject(c: Char) extends EventObject
case class MouseEventObject(x: Int, y: Int) extends EventObject

sealed trait EventDescriptor
case object KEY_EVENT extends EventDescriptor
case object MOUSE_EVENT extends EventDescriptor

class EventMap extends HMultiMap {
  class Mapping[K, V]

  object Mapping {
    implicit object k extends Mapping[KEY_EVENT.type, KeyEventObject => Unit]
    implicit object m extends Mapping[MOUSE_EVENT.type, MouseEventObject => Unit]
  }
}

这有点乱,但用法更漂亮:

val eventListeners = new EventMap

eventListeners.add(KEY_EVENT)((e: KeyEventObject) => println(e.c))
eventListeners.add(MOUSE_EVENT)((e: MouseEventObject) => println("X: " + e.x))
eventListeners.add(KEY_EVENT)((e: KeyEventObject) => println(e.c + " again"))

我们可以确认我们可以挑选出各种事件处理程序:

scala> eventListeners(KEY_EVENT).size
res3: Int = 2

我们可以假装触发一个事件并为其运行所有处理程序:

scala> eventListeners(KEY_EVENT).foreach(_(KeyEventObject('a')))
a
a again

而且这一切都是非常安全的,因为没有适当的证据,没有任何东西可以进入底层的松散类型的地图。例如,如果我们试图从 to 添加一个函数,我们会得到一个编译时String错误Unit

于 2012-12-13T02:04:02.963 回答
0

不可能。

假设你有这样的Map

val f = eventListeners(key).head

你会如何调用这个函数f?带有类型的参数EventObject?你不能。可能是KeyEventObject => Unit。带有类型的参数KeyEventObject?你不能。可能是MouseEventObject => Unit

您可以每次使用PartialFunction[EventObject, Unit]和检查,但这是一种丑陋的方式。isDefinedAt

于 2012-12-12T22:06:32.680 回答
0

编辑

似乎不可能。在 Scala-2.10 中也禁止逐个继承

...

也许通过使用一些特定的特征并声明事件对象类密封并扩展该特征?

val eventListeners = new MultiMap[EventDescriptor, ((_ >: EventTrait) => Unit)]

List来源:

:+[B >: A, That](elem: B)(implicit bf: CanBuildFrom[List[A], B, That]): That

That- 是一种特定类型,它可以通过隐式构建器从您的 EventTrait 构建,每个事件类型都有一个构建器。而不是elem: B尝试使用 classOf[B]。创建将MultiMap使用不同的 classOf 访问您的 get 方法:

def getMouseEvent(ed: EventDescriptor) = multimap.entrySet.filter(
(a, b) => a == ed ).map((a, b) => (a, convertTo(ClassOf[MouseEvent], b)).
filter((a, b) => b != null)

convertTo如果无法将事件转换为适当的类型,则返回 null

丑陋。

于 2012-12-12T22:06:49.047 回答