Scalaの依存性注入:Cakeパターン

私は最近Scalaの勉強を始めました。 どんな言語なのかまだ知らない人のために、公式ウェブサイトからの短い抜粋:



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!」という期待される結果が得られます。



次に、利点について少し説明します。





当然のことながら、依存性注入は、典型的なプロジェクトで必要なもののほんの一部です。 さらに、AOPは、たとえば、コード実行権の検証や宣言的なトランザクション管理など、他の多くのことを保証するために、まったく害を及ぼすことはありませんが、次回はこのことについて書きます。






All Articles