Scalaは簡潔でエレガントで静的に型付けされたプログラミング言語であり、オブジェクト指向言語と機能言語の機能を組み合わせています。 ScalaはJavaと完全に互換性があります。
今日は、この言語の豊富な表現能力を使用して、多かれ少なかれ大規模なプロジェクトに関連する問題、つまりコンポーネントの依存関係や依存関係の注入を解決する方法を紹介したいと思います。 過去数年間、私はこの問題を解決するためにSpring Iocを使用しましたが、このフレームワークにはいくつかの欠点があり、その最も明白なものは、実行時のコンポーネントからのアプリケーションのアセンブリとxml記述子の存在です(もちろん、自動配線と注釈を使用できますが、これらの機会には深刻な問題があります)。
もちろん、私は注意の欠如に苦しむかもしれませんが、私にとっての典型的な問題はコンポーネントを書くことです、私はしばしばxml記述子でそれを書くのを忘れるか、その依存関係のいくつかを書くのを忘れます。 この問題は、自動テストまたはアプリケーション自体を実行することにより、ランタイムでのみ検出できます。 そして、一般に、アプリケーションにxml記述子が存在することは、JSFを使用していた頃からいらいらさせていました。 記述子が少ないほど優れています。
うれしいことに、ScalaにはDIを整理するかなり簡潔な方法があり、そのような欠点はありません。 ケーキパターン自体は、Martin OderskyとMatthias Zengerが執筆した記事「Scalable Components Abstraction」で説明されています。 テンプレートの名前は、Jon PrettyがNabbleの議論で提案しました:「理論に対する理由は、ケーキに対する私の愛に加えて、ケーキは(ジャムで分離された)いくつかの層で作られ、スライスにカットできることです」。 名前が定着しました。
このテンプレートの実装の簡単な例を見てみましょう。 2つのアプリケーションコンポーネントを宣言することから始めましょう。
trait NameProviderComponent {
val nameProvider:NameProvider
trait NameProvider {
def getName:String
}
}
trait SayHelloComponent {
val sayHelloService:SayHelloService
trait SayHelloService {
def sayHello:Unit
}
}
コンポーネントインターフェースをNameProviderComponentコンテナーとSayHelloComponentコンテナーにパックする必要があるのはなぜですか。また、nameProvider:NameProvider定数とsayHelloService:SayHelloService定数が少し後に明らかになる理由は、今のところ、これらのコンポーネントの実装を記述します。 名前プロバイダーから始めましょう:
trait NameProviderComponentImpl extends NameProviderComponent {
class NameProviderImpl extends NameProvider {
def getName:String = "World"
}
}
ただし、依存関係を挿入するプロセスが明確になるまで、すべてが非常に簡単です。 アプリケーションで依存関係を持つ唯一のモジュールを考えてみましょう。
trait SayHelloComponentImpl extends SayHelloComponent {
this: SayHelloComponentImpl with NameProviderComponent =>
class SayHelloServiceImpl extends SayHelloService {
def sayHello:Unit = println("Hello, "+nameProvider.getName+"!")
}
}
仕組みを見てみましょう。 最初に注目されるのは構造です:“ this:SayHelloComponentImpl with NameProviderComponent =>”。この特性を実装するオブジェクトが持つべき型をコンパイラーに文字通り伝えます。 このような宣言の後、トレイトNameProviderComponentがそのような定数を宣言するため、sayHello関数の本体で未定義の定数nameProviderを使用することが可能になります。 これは、レイヤーを接続するのと同じジャムです。
ケーキ全体に移りましょう:
object ComponentRegistry
extends SayHelloComponentImpl
with NameProviderComponentImpl {
val nameProvider = new NameProviderImpl
val sayHelloService = new SayHelloServiceImpl
}
; ComponentRegistryオブジェクトは、モジュールの特性実装の両方を実装し、これまでに定義されていない最後のクラスメンバであるnameProviderおよびsayHelloService定数を定義します。 このオブジェクトから取得したsayHelloServiceは、nameProvider NameProviderImplという名前を取得するために使用されます。 ComponentRegistryは、SpringContextに直接対応しています。
使い方:
object MyApplication {
def main(args : Array[String]) : Unit = {
ComponentRegistry.sayHelloService.sayHello
}
}
このアプリケーションを実行すると、「Hello、World!」という期待される結果が得られます。
次に、利点について少し説明します。
- コンテキスト/レジストリの「アセンブリ」は、コンパイル時に部分的に行われます。 依存関係を忘れると、ビルドが壊れます。 不適切なタイプの依存関係でコンパイルすることはできません。 例:
object BrokenComponentRegistry
extends SayHelloComponentImpl {
val sayHelloService = new SayHelloServiceImpl
}
「不正な継承。完全に正気なメッセージでコンパイルを中断します。 self-type BrokenComponentRegistry.typeは、NameProviderComponentを持つSayHelloComponentImplのselftype SayHelloComponentImplに適合しません»
- これはそれほど重要ではありませんが、xml記述子の解析およびリフレクションAPIのアクティブな操作を必要としないため、アプリケーションの起動時間が短縮されます。
当然のことながら、依存性注入は、典型的なプロジェクトで必要なもののほんの一部です。 さらに、AOPは、たとえば、コード実行権の検証や宣言的なトランザクション管理など、他の多くのことを保証するために、まったく害を及ぼすことはありませんが、次回はこのことについて書きます。