scala api design:我可以避免不安全的演员表或泛型

时间:2015-10-30 20:41:53

标签: scala

我正在编写一个提供分布式算法的库。想法是现有的应用程序可以添加库以使用该算法。该算法位于库模块中,它通过网络后面的网络抽象实际数据传输。使用该算法的应用程序必须提供实际的网络传输代码。在代码中,它看起来如下所示:

// library is really a separate project not a single object
object Library {

  // handle to a remote server
  trait RemoteProcess

  // concrete server need to know how to actually send to a real remote process
  trait Server {
    def send(client: RemoteProcess, msg: String)
  }
}

// the application uses the library and provides the concrete transport
object AkkaDemoApplication {

  // concreate ref is a m wrapper to an actor ref in demo app
  case class ConcreteRemoteProcess(ref: akka.actor.ActorRef) extends Library.RemoteProcess

  class AkkaServer extends Library.Server {
    // **WARNING** this wont compile its here to make my question
    override def send(client: ConcreteRemoteProcess, msg: String): Unit = client.ref ! msg
  }
}

我考虑过几个选项:

  1. 让AkkaServer方法的签名重载库特征方法,然后对ConcreteRemoteProcess执行不安全的转换。育!
  2. 让AkkaServer方法的签名重载库trait方法,然后RemoteProcesss参数上的模式匹配给出ConcreteRemoteProcess。如果传递错误的东西,这在运行时爆炸方面并不比不安全的强制转换更好。
  3. 根据RemoteProcess使库服务器具有通用性。
  4. 选项3的示例如下:

    object Library {
    
      trait Server[RemoteProcess] {
        def send(client: RemoteProcess, msg: String)
      }
    }
    
    object Application {
      class AkkaServer extends Library.Server[ActorRef] {
        override def send(client: ActorRef, msg: String): Unit = client ! msg
      }
    }
    

    我尝试了选项3并且它工作但是泛型类型最终被标记在整个库模块中的每个类型上。然后有很多协变和逆变的麻烦来编译算法代码。只是为了在一个集成点获得编译时确定性,认知开销非常大。在视觉上,库代码由通用签名控制,就好像理解对于理解库是至关重要的,而实际上它对于理解库逻辑是完全分心的。

    所以使用遗传工作肯定给了我编译时间,但现在我希望我选择2(模式匹配)与借口"如果有人弄错了它会在启动时快速失败让我们继续很简单"。

    我在这里缺少一些Scala功能或成语,以获得编译时确定性而没有高触摸的认知开销"所有库代码都触及但忽略的通用?

    编辑我认为可能我的代码库被错误地考虑在内,因此重构可以将泛型移动到边界。然而,该库已经被重构为可测试性,并且分解为可测试的职责是泛型问题的一部分,这些问题在代码库中被涂抹了。因此我的问题是:一般来说,还有另一种技术我不知道要避免使用泛型来为抽象API提供具体的实现吗?

1 个答案:

答案 0 :(得分:3)

我认为你的算法与Akka的结合太紧密了。此外,我假设服务器将数据发送到执行某些操作的远程客户端并发送回结果

答案

为什么不

object Library {
  // handle to a remote server
  trait RemoteProcessInput

  trait RemoteProcessResult

  // concrete server need to know how to actually send to a real remote process and how to deal with the result
  trait Server {
    def handle(clientData: RemoteProcessInput) : Future[RemoteProcessResult] 
  }
}

具体实现提供了Akka的实现

object Application {
  class AkkaServerImpl(system: ActorSystem)
  extends Library.Server {
    override def handle(clientData: RemoteProcessInput)
  : ActorRef, msg: String): Future[RemoteProcessResult] = {
      // send data to client and expect result
      // you can distinguish target and msg from the concrete input
      val ref : ActorRef = ??? // (resolve client actor)
      val msg = ??? // (create your message based on concrete impl)
      val result = ref ? msg // using ask pattern here
                             // alternatively have an actor living on server side that sends msgs and receives the clients results, triggered by handle method
      result
    } 
}

}