0

为了能够处理大量不同的请求类型,我创建了一个.proto这样的文件:

message Message
{
   string typeId = 1;
   bytes message = 2;
}

我添加了,typeId以便知道实际protobuf bytes代表什么。(自我描述)

现在我的问题是以优雅的方式处理不同的“具体类型”。(注意:如果我简单地使用类似switch-case的方法,一切正常!)

我想到了这样的解决方案:

1)具有不同处理程序必须实现的特征,例如:

trait Handler[T]
{
  def handle(req: T): Any
}

object TestHandler extends Handler[Test]
{
  override def handle(req: Test): String =
  {
    s"A success, $req has been handled by TestHandler
  }
}

object OtherHandler extends Handler[Other]
{
  override def handle(req: Other): String =
  {
    s"A success, $req has been handled by OtherHandler
  }
} 

2) 提供某种注册表来查询给定消息的正确处理程序:

val handlers = Map(
    Test -> TestHandler,
    Other -> OtherHandler
  )

3)如果有请求进来,它会识别自己,所以我们需要另一个 Mapper:

val reqMapper = Map(
  "Test" -> Test
  "Other" -> Other
)

4)如果有请求进来,处理它:

val request ...
// Determine the requestType
val requestType = reqMapper(request.type) 
// Find the correct handler for the requestType
val handler = handlers(requestType)
// Parse the actual request
val actualRequest = requestType.parse(...) // type of actualRequest can only be Test or Other in our little example

现在,直到这里一切看起来都很好,花花公子,但是这条线打破了我的整个世界:

handler.handle(actualRequest)

它导致:

类型不匹配; 发现:com.trueaccord.scalapb.GeneratedMessage 与 Product 与 com.trueaccord.scalapb.Message [_ >: tld.test.proto.Message.Test 与 tld.test.proto.Message.Other <: com.trueaccord.scalapb。 GeneratedMessage with Product] with com.trueaccord.lenses.Updatable[_ >: tld.test.proto.Message.Other with tld.test.proto.Message.Test <: com.trueaccord.scalapb.GeneratedMessage with Product]{def companion :可序列化} 需要:_1

据我了解-如果有误,请在此处纠正我-编译器无法确定此处actualRequest是否可以由处理程序“处理”。这意味着它缺乏这样的知识,即actualRequest它肯定存在于其中,而且它mapper也存在一个handler

它基本上是人类可以获得的隐含知识,但编译器无法推断。

那么,话虽这么说,我怎样才能优雅地克服这种情况呢?

4

3 回答 3

1

当您使用普通地图时,您的类型会丢失。例如

object Test{}
object Other{}
val reqMapper = Map("Test" -> Test,"Other" -> Other)
reqMapper("Test")
res0: Object = Test$@5bf0fe62 // the type is lost here and is set to java.lang.Object

解决这个问题的最惯用的方法是使用模式匹配

request match {
  case x: Test => TestHandler(x)
  case x: Other => OtherHandler(x)
  case _ => throw new IllegalArgumentException("not supported")
}

如果您仍想使用 Maps 将您的类型存储到处理程序关系,请考虑HMap由 Shapeless here提供

异构映射

Shapeless 提供了一个异构映射,它支持键类型和对应的值类型之间的任意关系,

于 2017-02-04T00:44:25.293 回答
0

您可以使用的一个技巧是将伴随对象捕获为隐式,并将解析和处理组合在一个函数中,其中该类型可供编译器使用:

case class Handler[T <: GeneratedMessage with Message[T]](handler: T => Unit)(implicit cmp: GeneratedMessageCompanion[T]) {
  def handle(bytes: ByteString): Unit = {
    val msg: T = cmp.parseFrom(bytes.newInputStream)
    handler(t)
  }
}

val handlers: Map[String, Handler[_]] = Map(
  "X" -> Handler((x: X) => Unit),
  "Y" -> Handler((x: Y) => Unit)
)

// To handle the request:
handlers(request.typeId).handle(request.message)

另外,看看any.proto哪个定义了一个与你的Message. 它不会解决您的问题,但您可以利用它的优势packunpack方法。

于 2017-02-04T22:00:09.620 回答
0

我现在选择了这个解决方案(基本上是thesamet,有点适合我的特定用例)

trait Handler[T <: GeneratedMessage with Message[T], R]
{
    implicit val cmp: GeneratedMessageCompanion[T]
    def handle(bytes: ByteString): R = {
        val msg: T = cmp.parseFrom(bytes.newInput())
        handler(msg)
    }

    def apply(t: T): R
}

object Test extends Handler[Test, String]
{
    override def apply(t: Test): String = s"$t received and handled"

    override implicit val cmp: GeneratedMessageCompanion[Test] = Test.messageCompanion
}
于 2017-02-05T21:45:27.287 回答