使用反射,隐式和泛型的Scala设计

时间:2016-09-23 07:05:38

标签: scala design-patterns

我有不同的来源和相应的参数

Source1,Source2,Source3

Parameter1,Parameter2,Parameter3,

来源:是特质(可以改变)

trait Source[T] {
 def get(Parameter)(implicit c: Context): MyData[T]
}

参数也是一种特质

trait Parameter

我有不同的OutputType类:T1,T2,T3

我需要输出为:MyData [OutputType]

修复了API签名(对签名的更改不太可取):

val data1: MyData[T1] = MyAPI.get[T1](Parameter1("a", "b")) // this should give MyData from Source1 of type T1
val data2: MyData[T2] = MyAPI.get[T2](Parameter3(123)) // this should give MyData from Source3 of type T2

某些源支持某些输出类型(例如T1,T2),但有些可能不支持。

我做了什么: 我尝试使用scala反射typeTag来确定运行时的类型,但由于返回类型将是MyData [T],并且处于反变量位置,因此它不会知道实际的返回类型。 (Why does TypeTag not work for return types?) e.g。

object MyAPI {
  get[T: TypeTag](p: Parameter)(implicit c: Context): MyData[T] = {}
}

我也尝试过使用类型模式。 Scala TypeTag Reflection returning type T 我可以使用不同的OutputType为每个创建隐式val,但只适用于单个Source1。我无法为所有来源工作。

我试图这样做:

    object MyAPI {
      get[T: SomeConverter](p: Parameter)(implicit c: Context): MyData[T] = {
         p match {
          case Parameter1 => Source1[T].read(p.asInstanceOf(Parameter1)
          case Parameter2 => Source2[T].read(p.asInstanceOf(Parameter2)
         }
       }
    }

2 个答案:

答案 0 :(得分:1)

object Main extends App {
  sealed trait Parameter

  case class Parameter1(n: Int) extends Parameter with Source[Int] {
    override def get(p: Parameter): MyData[Int] = MyData(n)
  }

  case class Parameter2(s: String) extends Parameter with Source[String] {
    override def get(p: Parameter): MyData[String] = MyData(s)
  }

  case class MyData[T](t: T)

  trait Source[T] {
    def get(p: Parameter): MyData[T]
  }

  object MyAPI {
    def get[T](p: Parameter with Source[T]): MyData[T] = p match {
      case p1: Parameter1 => p1.get(p)
      case p2: Parameter2 => p2.get(p)
    }
  }

  val data1: MyData[Int] = MyAPI.get(Parameter1(15)) // this should give MyData from Source1 of type T1
  val data2: MyData[String] = MyAPI.get(Parameter2("Hello World")) // this should give MyData from Source3 of type T2

  println(data1)
  println(data2)
}

这样做你想要的吗?

ScalaFiddle:http://www.dlvsystem.com/

答案 1 :(得分:1)

免责声明:我想我想出了你想要的东西。我也在学习设计类型安全的API,所以这是一个。

提供的变体使用含义。您必须手动建立参数类型和它们产生的结果之间的映射,这可能包括也可能不包括源。但是,它在运行时不起作用,因此我也删除了常用特征Parameter。它也没有对来源施加任何限制。

它也“看起来”你希望它看起来的方式,但并不完全是这样。

case class User(id: Int) // Example result type

// Notice I totally removed any and all relation between different parameter types and sources
// We will rebuild those relations later using implicits
object Param1
case class Param2(id: Int)
case class Param3(key: String, filter: Option[String])

// these objects have kinda different APIs. We will unify them.
// I'm not using MyData[T] because it's completely irrelevant. Types here are Int, User and String
object Source1 {
  def getInt = 42
}

object Source2 {
  def addFoo(id: Int): Int = id + 0xF00
  def getUser(id: Int) = User(id)
}

object Source3 {
  def getGoodInt = 0xC0FFEE
}

// Finally, our dark implicit magic starts
// This type will provide a way to give requested result for provided parameter
// and sealedness will prevent user from adding more sources - remove if not needed
sealed trait CanGive[Param, Result] {
  def apply(p: Param): Result
}

// Scala will look for implicit CanGive-s in companion object
object CanGive {
  private def wrap[P, R](fn: P => R): P CanGive R =
    new (P CanGive R) {
      override def apply(p: P): R = fn(p)
    }

  // there three show how you can pass your Context here. I'm using DummyImplicits here as placeholders
  implicit def param1ToInt(implicit source: DummyImplicit): CanGive[Param1.type, Int] =
    wrap((p: Param1.type) => Source1.getInt)

  implicit def param2ToInt(implicit source: DummyImplicit): CanGive[Param2, Int] = 
    wrap((p: Param2) => Source2.addFoo(p.id))

  implicit def param2ToUser(implicit source: DummyImplicit): CanGive[Param2, User] =
    wrap((p: Param2) => Source2.getUser(p.id))

  implicit val param3ToInt: CanGive[Param3, Int] = wrap((p: Param3) => Source3.getGoodInt)

  // This one is completely ad-hoc and doesn't even use the Source3, only parameter
  implicit val param3ToString: CanGive[Param3, String] = wrap((p: Param3) => p.filter.map(p.key + ":" + _).getOrElse(p.key))
}


object MyApi {
  // We need a get method with two generic parameters: Result type and Parameter type
  // We can "curry" type parameters using intermediate class and give it syntax of a function
  // by implementing apply method
  def get[T] = new _GetImpl[T]

  class _GetImpl[Result] {
    def apply[Param](p: Param)(implicit ev: Param CanGive Result): Result = ev(p)
  }

}

MyApi.get[Int](Param1) // 42: Int
MyApi.get[Int](Param2(5)) // 3845: Int
MyApi.get[User](Param2(1)) // User(1): User
MyApi.get[Int](Param3("Foo", None)) // 12648430: Int
MyApi.get[String](Param3("Text", Some(" read it"))) // Text: read it: String

// The following block doesn't compile
//MyApi.get[User](Param1) // Implicit not found
//MyApi.get[String](Param1) // Implicit not found
//MyApi.get[User](Param3("Slevin", None)) // Implicit not found
//MyApi.get[System](Param2(1)) // Same. Unrelated requested types won't work either