ケーキのパターン
1. 概要
Scalaの自己型アノテーションに関するチュートリアルでは、自己型アノテーションを使用して型間の依存関係を宣言する方法を説明しました。
さて、さらに先に進む時が来ました。 Scalaでの依存性注入パターンの慣用的な実装であるCakePatternについて話しましょう。
2. 環境の設定
前のチュートリアルで定義した特性とクラスを刷新することから始めます。 テストを実行するためのフレームワークを設定したいとします。 実行環境を表すタイプが必要です。
trait TestEnvironment {
val envName: String
def readEnvironmentProperties: Map[String, String]
}
そして、テストを実行するものであるタイプ:
class TestExecutor { env: TestEnvironment =>
def execute(tests: List[Test]): Boolean = {
println(s"Executing test with $envName environment")
tests.forall(_.execute(readEnvironmentProperties))
}
}
テストを実行するには、エグゼキュータにTestEnvironmentのインスタンスが必要です。 Scalaでは、自己型アノテーションを使用してこの制約を表現できます。 表記env:TestEnvironment => は、タイプ TestEnvironment からの依存関係を宣言し、envと呼びます。
依存性注入パターンの完全な実装を作成するためのベースとして自己型アノテーションを使用する方法を見てみましょう。
3. 慣用的な依存性注入:ケーキパターン
自己型を使用して、型間の依存性の一種の解決メカニズムを構築できます。これは、慣用的なScala構造のみを使用する一種の依存性注入メカニズムであるCakeパターンと呼ばれます。
ケーキパターンは、Scalaでのセルフタイプアノテーションの最も重要な使用法を表しています。
このパターンは、依存関係管理の2つの異なる側面を定義します。 1つ目は、依存関係を宣言する方法です。 2つ目は、依存関係を解決する方法です。
3.1. 依存関係の宣言
前の例では、ビジネスロジックと依存関係の解決を1つのタイプに混合しました。 より良いアプローチは、依存関係の管理に使用されるコードからビジネスロジックを分離することです。
まず、セルフタイプのアノテーションをTestExecutorクラスから持ち上げましょう。 タイプを囲む新しいスコープを定義する必要があります。
trait TestEnvironmentComponent {
val env: TestEnvironment
trait TestEnvironment {
val envName: String
def readEnvironmentProperties: Map[String, String]
}
}
スコープを識別するために、次の特性を使用します TestEnvironmentComponent。 The テスト環境 インスタンスは、 環境 スコープで宣言された変数。 特性 TestEnvironmentComponent すべての可用性に責任があります TestExecutor アプリケーション間のクラスインスタンス。 この場合、変数 環境 使用したのでシングルトンを表します val 参照。
スコープの定義にも進みます TestExecutor クラス。 このスコープオブジェクトに依存関係宣言を配置できます。
trait TestExecutorComponent { this: TestEnvironmentComponent =>
val testExecutor: TestExecutor
class TestExecutor {
def execute(tests: List[Test]): Boolean = {
println(s"Executing test with ${env.envName} environment")
tests.forall(_.execute(env.readEnvironmentProperties))
}
}
}
前のコードの重要なポイントは次のとおりです。
- 依存関係の宣言は、 TestExecutorComponent 特性
- からの依存関係 テスト環境 タイプはのコードを汚染しません TextExecutor もうクラス
- アクセスします テスト環境 インスタンスを介して 環境 変数
単純な依存関係以上のものを注入する必要がある場合は、複数の特性をスタックする自己型アノテーションを使用できます。 ロギングフレームワークを使用するエグゼキュータが必要だと想像してください。
trait LoggingComponent {
val logger: Logger
class Logger {
def log(level: String, message: String): Unit =
println(s"$level - $message")
}
}
エグゼキュータでLoggingComponentを、TestEnvironmentComponentと混在させることができます。
trait TestExecutorComponentWithLogging { this: TestEnvironmentComponent with LoggingComponent =>
val testExecutor: TestExecutor
class TestExecutor {
def execute(tests: List[Test]): Boolean = {
logger.log("INFO", s"Executing test with ${env.envName} environment")
tests.forall(_.execute(env.readEnvironmentProperties))
}
}
}
今やらなければならないのは、依存関係を解決することだけです。 それを見てみましょう。
3.2. 依存関係の解決
これまで、依存関係を宣言できる環境を設定してきました。 それらをエレガントに解決する時が来ました。
object Registry extends TestExecutorComponent with TestEnvironmentComponent {
override val env: Registry.TestEnvironment = new WindowsTestEnvironment
override val testExecutor: Registry.TestExecutor = new TestExecutor
}
Registry オブジェクトは、TestExecutorComponentトレイトとTestEnvironmentComponentトレイトの両方を拡張します。 したがって、コンパイラは、具象インスタンスを使用して自己型注釈を解決できます。
ケーキパターンにより、さまざまなレジストリオブジェクトを宣言できます。 たとえば、TestExecutorクラスの単体テストを作成する必要があるとします。 TestEnvironment をモックし、モックされた参照をエグゼキューターオブジェクトに挿入する必要があります。
まず、レジストリオブジェクトをトレイトに変換します。 次に、それを使用して、適切なクラスのインスタンスを単体テストに組み込むことができます。
たとえば、タイプ TestExecutor の単体テストを作成するには、タイプTestEnvironmentのすべてのインスタンスをモックする必要があります。
trait TestRegistry
extends CakePattern.TestExecutorComponent
with CakePattern.TestEnvironmentComponent
with MockFactory {
override val env: TestEnvironment = mock[TestEnvironment]
override val testExecutor: TestExecutor = new TestExecutor
}
変数をモックします 環境 モッキングライブラリScalaMockを使用して、コンパイラにそのような参照を挿入させます。 testExecutor 変数。
4. 結論
Scalaの自己型アノテーションで見た概念から始めて、Scala言語の慣用的な構成要素であるCake Patternのみを使用して、型間の依存関係を解決するための完全なフレームワークを構築しました。
いつものように、このチュートリアルのソースコードはGitHubでから入手できます。