如何在Scala中实现类型安全回调系统?

时间:2012-10-10 19:54:49

标签: oop scala callback

我有一个通过套接字连接的客户端 - 服务器应用程序。我的MessageHandler类负责处理传入和传出的消息。我传递必要的参数来填充请求和回调,我希望在收到响应后调用它。我在具有唯一请求标识符的哈希映射中存储回调。收到响应后,我从哈希映射中获取回调,调用它并将响应作为参数传递。这是代码

class MessageHanlder {
  val callbacks = new HashMap[String, (AnyRef) => Unit]

  def sendAuthRequest(login: String, password: String, callback: Option[(AnyRef) => Unit]) {
    val requestId = generateRequestId()
    // create a packet with requestId, login and password
    // send the packet
    if(callback.isDefined) callbacks += ((requestId, callback.get))
  }

  private def generateRequestId() = // returns random string

  def handleAuthResponse(authResponse: AuthResponse) {
    val requestId = authResponse.requestId
    val callbackOption = callbacks.get(requestId)
    if(callbackOption.isDefined) callbackOption.get(authResponse)
  }

  def sendServerInfoRequest(callback: Option[(AnyRef) => Unit]) {
    val requestId = generateRequestId()
    // create a packet with requestId
    // send the packet
    if(callback.isDefined) callbacks += ((requestId, callback.get))
  }

  def handleServerInfoResponse(serverInfoResponse: ServerInfoResponse) {
    val requestId = serverInfoResponse.requestId
    val callbackOption = callbacks.get(requestId)
    if(callbackOption.isDefined) callbackOption.get(serverInfoResponse)
  }

我的问题是回调的参数类型。它可以是ServerInfoResponse或AuthResponse或任何其他响应类型。每个响应都有自己的一组字段,我想从回调中访问它们。要将回调保存到hashmap中,我必须将参数类型概括为AnyRef。所以在回调中我必须将AnyRef转换为具体类型,如此

val serverInfoCallback = (response: AnyRef) => {
  val serverInfoResponse = response.asInstanceOf[ServerInfoResponse] // explicit cast
  val name = serverInfoResponse.name
  val numberOfCores = serverInfoResponse.numberOfCores
  // so on
}

有没有办法避免施法?或者是否有更正确的方法来实现回调系统?

谢谢!

2 个答案:

答案 0 :(得分:4)

如果响应类型不是静态已知的,您可以创建一个密封的特征响应,并使其他类型扩展它。

然后你可以使用模式匹配和一些关于检查所有情况的编译器保证。如果你不能使这些类型延伸到一个密封类型,你可以使用模式匹配,但编译器不会帮助你。

如果响应类型是静态已知的,您是否可以在问题中明确类型关系?

答案 1 :(得分:3)

我发现您的问题非常有趣,并尝试使用令人难以置信的shapeless库找到类型安全的解决方案。我们走了:

基本

/* Responses get send to the callbacks */
abstract class Response

/* Callback ids identify callbacks and also specify the type of response
 * a corresponding callback accepts.
 */
abstract class CallbackId[T <: Response]

暗示确保类型安全

/* Shapeless magic that ensures a type-safe mapping from identifiers to
 * callbacks. Consider an implicit of type CME[CallbackId[R], R => Unit]
 * as the evidence that "an id promising to identify a callback that
 * accepts a response R actually maps to such a function."
 */
class CME[-K, V] /* CallbackMapEntry */

implicit val acceptAppleResponse =
  new CME[CallbackId[AppleResponse], AppleResponse => Unit]

implicit val acceptPearResponse =
  new CME[CallbackId[PearResponse], PearResponse => Unit]

implicit val acceptAnyResponse =
  new CME[CallbackId[Response], Response => Unit]

响应

/* Define some responses */
case class AppleResponse() extends Response
case class PearResponse() extends Response
case class PruneResponse() extends Response

回调

/* Define some callbacks */

val appleResponseCallback1 = (r: AppleResponse) => {
  println("[appleResponseCallback1]")
}

val appleResponseCallback2 = (r: AppleResponse) => {
  println("[appleResponseCallback1]")
}

val pearResponseCallback = (r: PearResponse) => {
  println("[pearResponseCallback]")
}

val anyResponseCallback = (r: Response) => {
  println("[anyResponseCallback] r is a " + r.getClass.getSimpleName)
  r match {
    case appleR: AppleResponse => // ...
    case pearR: PearResponse => // ...
    case pruneR: PruneResponse => // ...
  }
}

标识符

/* A couple of identifiers */
object appleCbId1 extends CallbackId[AppleResponse]
object appleCbId2 extends CallbackId[AppleResponse]
object pearCbId1 extends CallbackId[PearResponse]
object pearCbId2 extends CallbackId[PearResponse]
object someCbId extends CallbackId[Response]

类型安全的回调列表

/* Init list of callbacks */
val callbacks = HMap[CME](
  appleCbId1 -> appleResponseCallback1,
  appleCbId2 -> appleResponseCallback2,
  pearCbId1 -> pearResponseCallback,
  pearCbId2 -> pearResponseCallback,
  someCbId -> anyResponseCallback
)

第一个用例

val appleCb = callbacks.get(appleCbId1).get
val someCb = callbacks.get(someCbId).get

appleCb(AppleResponse()) /* Fine */
someCb(AppleResponse())  /* Fine */
someCb(PearResponse())   /* Fine */
// appleCb(PruneResponse()) /* Rejected by the compiler */

介绍请求

abstract class Request[R <: Response] {
  def id: CallbackId[R]
}

case class AppleRequest(id: CallbackId[AppleResponse])
  extends Request[AppleResponse]

case class PearRequest(id: CallbackId[PearResponse])
  extends Request[PearResponse]

case class RandomRequest(id: CallbackId[Response])
  extends Request[Response]

第二个用例

def handleAppleRequest(r: AppleRequest) {
  // Do something with the request

  // Phone home
  val cb = callbacks.get(r.id).get
  cb(AppleResponse()) /* Fine */
  // cb(PearResponse())  /* Rejected by the compiler */
}

handleAppleRequest(AppleRequest(appleCbId1))

由于解决方案是类型安全的(或至少尝试过),因此在较少“静态”的环境中初始化回调列表可能会更复杂,例如,如果回调是由(弱类型)创建的工厂或反思。