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

Oop 如何在Scala中实现类型安全回调系统?,oop,scala,callback,Oop,Scala,Callback,我有一个通过套接字连接的客户机-服务器应用程序。我的MessageHandler类负责处理传入和传出的消息。我传递填充请求和回调所需的参数,我希望在收到响应后调用这些参数。我将回调存储在具有唯一请求标识符的哈希映射中。当收到响应时,我从哈希映射得到回调,调用它并将响应作为参数传递。这是密码 class MessageHanlder { val callbacks = new HashMap[String, (AnyRef) => Unit] def sendAuthRequest

我有一个通过套接字连接的客户机-服务器应用程序。我的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
}
有没有办法避免铸造?还是有更正确的方法来实现回调系统


谢谢

如果反应类型不是静态已知的,你可以做出一个封闭的特质反应,并让其他类型的人扩展它

然后,您可以使用模式匹配和一些编译器保证来检查所有案例。如果您不能使这些类型扩展一个密封类型,您可以使用模式匹配,但编译器不会帮助您


如果反应类型是静态已知的,你能在问题中弄清楚类型关系吗?

如果反应类型不是静态已知的,你可以做出一个密封的特质反应,并让其他类型扩展它

然后,您可以使用模式匹配和一些编译器保证来检查所有案例。如果您不能使这些类型扩展一个密封类型,您可以使用模式匹配,但编译器不会帮助您


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

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

基础 响应 回调 标识符 类型安全回调列表 第一个用例 介绍请求
由于解决方案是类型安全的,或者至少尝试是类型安全的,因此在不太静态的环境中初始化回调列表可能会更复杂,例如,如果回调是由弱类型工厂或反射创建的。

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

基础 响应 回调 标识符 类型安全回调列表 第一个用例 介绍请求
由于解决方案是类型安全的,或者至少尝试是类型安全的,因此在不太静态的环境中初始化回调列表可能会更复杂,例如,如果回调是由弱类型工厂或反射创建的。

是的,响应类型是静态已知的,并且可以扩展响应。这里的模式匹配看起来像是对类固醇的替代,但无论如何,它比我现在拥有的要好。谢谢。是的,响应类型是静态已知的,可以扩展密封特征响应。这里的模式匹配看起来像是对类固醇的替代,但无论如何,它比我现在拥有的要好。谢谢。别忘了hashmap必须正确允许并发,例如,您可以使用ConcurrentHasMap作为实现类。否则你会面临种族危险。谢谢你,瑞克,这真的很重要。但是,在我的实际应用程序中,MessageHandler是Akka actor,常规HashMap不是问题。不要忘记,HashMap必须正确允许并发,例如,您可以使用ConcurrentHasMap作为实现类。否则你会面临种族危险。谢谢你,瑞克,这真的很重要。然而,在我真正的应用程序中,MessageHandler是Akka actor,常规HashMap不是问题。。。非常好的回答:对我来说看起来有点复杂,我认为这对我来说太过分了。无论如何,我希望这个答案能帮助其他人。谢谢。@Soteric是的,这不是最复杂的一个,我同意。但是如果您将代码片段分为幕后必须做的事情和客户端最终必须做的事情,那么后面的部分就会变得非常小。也就是说,客户机只需实现回调并将其与标识符一起添加到可用回调列表中。这种不成形的方法不会影响性能吗?有一大笔开销吗?@Soteric简短回答:我不知道。我假设不是,因为shapeless主要关注精确的静态类型信息,这是在编译时完成的工作,而不是在运行时完成的。由于隐式方法调用和附加方法调用参数(用于帮助类型检查器/推断器的隐式证据参数),可能会有一些开销。然而,我不认为这些会造成很大的运行时开销。如果你搜索隐式方法的性能开销,你会发现大量的资源,其中一些声称JVM基本上能够完全删除这些
没有提到确切的管理费用百分比。我只是想知道是否涉及到任何繁重的操作,比如反射或任何广泛的垃圾生成方法。如果这只是几个方法调用的问题,那么也没关系。我将进一步研究它,稍后更新。谢谢你的支持。哇。。。非常好的回答:对我来说看起来有点复杂,我认为这对我来说太过分了。无论如何,我希望这个答案能帮助其他人。谢谢。@Soteric是的,这不是最复杂的一个,我同意。但是如果您将代码片段分为幕后必须做的事情和客户端最终必须做的事情,那么后面的部分就会变得非常小。也就是说,客户机只需实现回调并将其与标识符一起添加到可用回调列表中。这种不成形的方法不会影响性能吗?有一大笔开销吗?@Soteric简短回答:我不知道。我假设不是,因为shapeless主要关注精确的静态类型信息,这是在编译时完成的工作,而不是在运行时完成的。由于隐式方法调用和附加方法调用参数(用于帮助类型检查器/推断器的隐式证据参数),可能会有一些开销。然而,我不认为这些会造成很大的运行时开销。如果你搜索隐式方法的性能开销,你会发现大量的资源,其中一些声称JVM基本上能够完全删除它们。我没有提到确切的开销百分比。我只是想知道是否涉及到任何繁重的操作,比如反射或任何广泛的垃圾生成方法。如果这只是几个方法调用的问题,那么也没关系。我将进一步研究它,稍后更新。谢谢你的支持。
/* 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))