Scala で、コンストラクター注入用の単純な依存性注入フレームワークを作成しています。DI されたオブジェクトは、必要なサービスを通常のパラメーターのようにコンストラクターに配置し、どの引数がコンテナーから取得され、どの引数がインスタンス化時にユーザーによって渡されるかを決定する型クラスを実装するという考え方です。
したがって、次のようになります。
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
を as として呼び出して、コンストラクターに渡して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 の優れた回答 + わずかな変更のおかげで、1 つの型パラメーターのみを必要とし、すべての異なる引数リストの長さに対して機能するメソッドを次に示します。これは、リフレクションのコストなしで、依存関係を簡単に接続するための非常に簡単な方法です。
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)