我在Scala中为构造函数注入创建了一个简单的依赖注入框架。这个想法是DI'd的对象将它们所需的服务放在它们的构造函数中,就像常规参数一样,并实现一个类型类,确定它们从容器中取出哪些参数,以及用户在实例化时传递哪些参数。
所以,它应该看起来像:
trait Container {
private singletons: Map[Class, AnyRef]
def getSingleton[T: Manifest] =
singletons(implicitly[Manifest[T]].erasure).asInstanceOf[T]
... methods for adding singletons, etc ...
}
class Foo(arg: String, svc: FooService) {
...
}
trait Constructor[T] { ??? }
object FooConstructor extends Constructor[Foo] {
def construct(arg: String)(implicit container: Container) =
new Foo(arg, container.getSingleton[FooService])
}
现在基本上我希望能够有一个名为construct
的方法,我可以将其称为construct[Foo]("asd")
,并使用Foo
获取"asd"
的新实例传入构造函数,FooService
从本地容器获取并传递给构造函数。我们的想法是它应该为Constructor
获取Foo
类型的实例,并以类型安全的方式知道它应该具有的参数的数量和类型。此外,这是困难的部分,我不想写出参数的类型 - 只是要构造的对象。
我尝试了几件事:
trait Constructor1[T, A] {
def construct(arg: A): T
}
trait Constructor2[T, A1, A2] {
def construct(arg1: A1, arg2: A2): T
}
def construct[T, A](arg1: A): T = implicitly[Constructor1[T, A]].construct(arg1)
...
这种方法不起作用,因为它似乎是为了“召唤”Constructor
类型类实例,我们需要编写参数的类型,这是很多讨厌的样板:
construct[Foo, String]("asd") // yuck!
有没有办法使用类型类(或其他任何东西)来排序部分推断类型参数?我们在Foo
实例定义中定义了Constructor
的构造函数参数的类型,所以如果我们可以召唤实例,我们应该只能调用construct
并获得正确的参数类型。问题是获取该实例而不必指定构造函数类型参数。我已经玩了很多不同的想法,我觉得Scala的力量和技巧只是有是我可以写construct[Foo]("asd")
的方式并拥有参数列表是类型安全的。有什么想法吗?
更新:感谢Miles Sabin的优秀答案+稍作修改,这里的方法只需要一个类型参数,适用于所有不同的参数列表长度。这是一种非常简单的方法,可以轻松地连接依赖项,而无需反映成本:
trait Constructor1[T, A] { def construct(arg1: A)(implicit c: Container): T }
trait Constructor2[T, A, B] { def construct(arg1: A, arg2: B)(implicit c: Container): T }
implicit object FooConstructor extends Constructor1[Foo, String] {
def construct(arg1: String)(implicit c: Container) =
new Foo(arg1, c.getSingleton[FooService])
}
implicit object BarConstructor extends Constructor2[Bar, String, Int] {
def construct(arg1: String, arg2: Int)(implicit c: Container) =
new Bar(arg1, arg2, c.getSingleton[FooService])
}
class Construct[T] {
def apply[A](arg1: A)(implicit ctor: Constructor1[T, A], container: Container) =
ctor.construct(arg1)
def apply[A, B](arg1: A, arg2: B)(implicit ctor: Constructor2[T, A, B], container: Container) =
ctor.construct(arg1, arg2)
}
def construct[T] = new Construct[T]
construct[Foo]("asd")
construct[Bar]("asd", 123)
答案 0 :(得分:8)
Scala中的类型参数推断是一个全有或全无的事情:如果您明确提供类型参数块的任何类型参数,那么您必须提供所有这些参数。因此,如果您只想提供一组类型参数,则必须安排它们属于单独的类型参数块。
在这种情况下,这样做的方法是将construct
方法分为两个阶段:第一阶段,它采用显式类型参数并返回类似函数的值;第二个,它将类似函数的值应用于您希望推断类型的参数。
这是怎么回事,
// Function-like type
class Construct1[T] {
def apply[A](arg1: A)(implicit ctor : Constructor1[T, A]): T =
ctor.construct(arg1)
}
def construct[T] = new Construct1[T]
调用construct[Foo]
的结果是类型Construct1[Foo]
的值。这有一个apply
方法,带有可以推断的类型参数,以及一个隐式参数,其类型由T
和A
决定。您想要进行的调用现在看起来像
construct[Foo].apply("asd") // T explicit, A inferred as String
Scala围绕apply
的语义加糖规则适用于此,这意味着可以将其重写为,
construct[Foo]("asd")
这正是你想要的结果。
答案 1 :(得分:0)
trait Construct[T, A] {
def apply(arg: A): T
}
class Constructor[T]{
def apply[A](arg : A)(implicit construct : Construct) = construct(arg)
}
object Constructor {
def apply[T] = new Constructor[T]
}
Constructor[T]("arg")