この記事は、既製のライブラリに関するものではありません(数日でテストを実装しましたが、読者が同じことをするのは難しくないと思います)。むしろ、新しいアイデアを提示する試みです。 私の観点からは、これは非常に興味深いものです。
大規模なプロジェクトを開発する際の問題の1つは、依存関係の問題です。 からなる
-「上」に作成されたが、メソッド呼び出しの階層の奥深くに必要な場合、オブジェクトの目的のインスタンスを取得する場所
-他の実装を代用できるように、このインスタンスの受信を制御する方法は? まず、テストに関連する
-アイテム1とアイテム2を実装するフレームワークの設定でタンバリンと長いダンスをせずに任意のコードを起動できるようにこれを行う方法
サイクリングや他の致命的な罪の告発を避けるために、良いコードと良いライブラリのアイデアを教えてください。 一致しない場合は、コメントを控えてください。 要するに、ライブラリは、複雑ではあるが特定のタスクを解決するか、ドキュメントを読むよりもソースコードの学習を容易にするために十分な量でなければなりません。 フレームワークは、それを「機能させる」ために何らかの魔法を探している人を対象としています。 経験がある場合は、「万人向け」に書かれたフレームワークの数メガバイトのソースを研究するよりも、特定のタスク用に研ぎ澄まされた独自のコードを作成し、実装し、狭帯域の高品質ライブラリから実装をリンクする方が実用的です。
私はSpringさえも考えません。J2EEの軽量な代替と考えられていた時代はもう過ぎ去りました。 「オブジェクト」にラップされた静的変数を使用した標準的なスカロフアプローチは、理解できません。テストができず、設定が困難です。 ケーキパターンにも同じ欠点があります。 Guiceと同様のライブラリが残っています。
Guiceはどうですか? Guiceが大好きです。 ほとんど侵入的ではない構成はモジュール式であり、作業コードから分離されています。インジェクターの初期化中に個々のオブジェクトを置き換える機能です。 しかし:
-デフォルトでは、新しいオブジェクトは使い捨てで作成され、シングルトーンではありません。 誰がこれを発明したのですか? スコープを毎回明示的に示すことにうんざりしていませんか?
-反射。 アプリケーションの開始時間に悪影響を及ぼしますが、これはsbt-revolverの実行中に非常に悪い結果になります。
-モジュールの構成と再定義はそれほど便利ではありません。
-ここでは発明されていません(冗談)
そのため、完成したインジェクターをThreadLocal変数に保存し、静的メソッドを使用してインスタンスを取得します。 このようなもの:
たくさんのコード trait InjectorKey[T] /** * The injector - main interface used to bind code to this injector and access stored objects directly */ trait Injector { def let[T](func: => T): T def getInstance[T](classTag: ClassTag[T]): Option[T] def getInstance[T](key: Any, classTag: ClassTag[T]): Option[T] def eagerInit(): Unit } object Injector { private[depend] val context = new ThreadLocal[Injector]() def inject[T](implicit classTag: ClassTag[T]): T = injector.getInstance(classTag).getOrElse { throw new InjectorException("No instance registered for " + classTag) } def inject[T](key: InjectorKey[T])(implicit classTag: ClassTag[T]): T = injector.getInstance(key, classTag).getOrElse { throw new InjectorException("No instance registered for " + classTag + ", key=" + key) } def inject[T](key: Any)(implicit classTag: ClassTag[T]): T = injector.getInstance(key, classTag).getOrElse { throw new InjectorException("No instance registered for " + classTag + ", key=" + key) } def injector: Injector = { val r = context.get() if (r == null) { throw new IllegalStateException("There is no injector in current context. Forgot to run Injector.let for this thread?") } r } }
インジェクターの実装は、単純な連想配列[オブジェクトタイプ]-> [インスタンスを返すラムダ]とシングルトーンのキャッシュです。
依存関係を必要とするコードでは、inject [Type]を使用してオブジェクトを取得します。 もちろん、コンストラクターパラメーターの既定値として使用することをお勧めします。
それは何を与えますか?
-シングルトンではなくオブジェクトが必要な場合は、新しいMyClass()を使用して単純に作成できます。
-スコープのサポートは必要ありません-ThreadLocalからの現在のインジェクターは、セッションレベルインジェクターから最初にオブジェクトを要求するデリゲートでラップできます。
-反射なし
-インジェクターはドメインオブジェクトで使用できます! これは、ドメインモデルにロジックをフラッシュするすべてのファンにとって素晴らしいニュースだと思います。
-コードの任意の場所(特にテスト!)で、インジェクタを簡単に交換して、目的の動作を実現できます。
もちろん、インジェクターを呼び出し元のコードから他のスレッドに転送するためにアプリケーションで使用されるスレッドプールの改良が必要ですが、これはそれほど難しくありません。
テスト、それらは使用例でもあります class InjectorTest extends FunSuite { import Injector.inject test("basic query by type") { val i = Injector.newModule() .bind[String]("hello") .injector() i.let { assert(inject[String] == "hello")
どう思いますか?