« astahとExcelの間でマインドマップをやり取りする方法はコピペだけ | トップページ | XPエクストリームプログラミングは偉大だ~時代がその設計思想に追いついた »

2022/11/14

JavaのモジュールシステムでSPIとDIを実現するやり方

JavaGold黒本を読んでいたら、JavaのモジュールシステムでSPIとDIを実現するやり方をやっと理解できた。
理解できたことをラフなメモ。

【参考】
Javaのモジュールシステムの考え方をまとめてみた: プログラマの思索

クラウド上の開発がJavaに与えた影響は何なのか: プログラマの思索

Javaのモジュールシステムは複雑性をより増している: プログラマの思索

Java 9のモジュール機能「サービス(SPI)」と既存ライブラリの共存 (1/2)|CodeZine(コードジン)

DI(依存性の注入)とは依存性を注入するということである、、? - Qiita

module-info.javaに下記のような記述がある。
Sampleモジュールはどんな構造を持っているのか?

module Sample {
exports test;
uses test.Hello;
}

Sampleモジュールは、testパッケージを公開しており、test.Helloインターフェイスの実装クラスを利用する。
つまり、Sample単体ではコンパイルできるけれど、実装クラスがセットでなければ動作しない。

SPI(Service Provider Interface)を実装したHelloモジュールがない場合、コンパイルエラーにならない。
実際、ServiceLoader.load()は空のServiceLoaderインスタンスを返却するだけでスキップされる。
こんなプログラムになるだろう。

ServiceLoader loader = ServiceLoader.load(Hello.class);
Iterator iter = loader.iterator();
while (iter.hasNext()) {
Hello obj = iter.next();
System.out.println(obj.sayHello());
}

つまり、META-INF/services/helloというテキストファイルから読み込む時に、実装クラスが書いてなければ、ループ処理は空回りするだけ。

では、test.Helloインターフェイスの実装クラスtest.impl.HelloImplを持つHelloモジュールはどんな構造を持つのか?
たぶんこんな構造だろう。

module Hello {
exports test.impl;
requires Sample;
provides test.Hello with test.impl.HelloImpl;
}

Helloモジュールは、test.implパッケージを公開して、Helloの実装クラスHelloImplを提供する。
この時、HelloモジュールがSampleモジュールを呼び出し、Sampleへの依存関係を持つ点が重要。
なぜならば、test.Helloを利用するには、定義されているSampleモジュールをImportする必要があるから。

すると、SampleとHelloは依存性の注入(DI:Dependency Injection)を実現していることになる。
なぜならば、SPIの定義より、クライアント側Sampleはライブラリ側Helloの実装内容を利用しているのに、プログラムの依存関係としては、HelloからSampleへ逆向きの依存関係を持っているからだ。

つまり、普通は、Sample(利用側) --> Hello(提供側) になるはずだが、Sample(利用側) <-- Hello(提供側) なので、依存関係が逆転している。

まとめると、
Sample --> testパッケージ --> test.Helloインターフェイス
Hello --> Sample --> test.Helloインターフェイス
Hello --> test.impl.HelloImplクラス
という依存関係を持つ。

DIが実現されていることによってどんなメリットがあるのか?

まず、test.Helloインターフェイスを実装した具象クラスは、Sampleモジュールのモジュールパスに含める必要はない。
なぜならば、SPIの実装は後から提供されるので、実装に依存する必要はないからだ。
つまり、最初にIFだけ定義して公開しておけば、SPIの実装側ライブラリは後から実装すればいい。
IFという約束事だけ定義して、具体的な中身は後から開発できるので、開発スケジュールを調整しやすく、仕様変更にも柔軟に対応できる。

次に、test.Helloインターフェイスを実装するモジュールは、Sampleモジュールを再コンパイルする必要はない。
なぜならば、SPIの拡張ポイントとなるインターフェイスは変わらない限り、実装クラスに依存しないからだ。
つまり、SPIを利用する側のSampleモジュールと、実装クラスを提供するHelloは独立してコンパイルできる。

CやJavaなどでは、リプレース時にリコンパイルが必要だったりして、開発環境を準備したり、リリースするときは割と面倒だが、リコンパイルが不要であれば、過去にコンパイルしたモジュールをそのまま利用すればいい。

このモジュールシステムのサンプルから、僕はJDBCドライバの実装を連想した。
JDBCドライバでは、JDBCドライバのIFは既に定義されているが、各RDBごとにJDBCドライバの実装は異なる。
つまり、ユーザ側はJDBCドライバのIFを使ってプログラムを組めば良く、各RDBの実装の違いを意識する必要はない。
後から、利用するRDBのJDBCドライバを組み込めばいい。

2000年代にDIのアイデアが出た時はすごい設計技法のようなイメージだったが、2020年代の今となっては枯れた技術の一つにすぎない。
しかし、こういう依存関係を逆転する技術を利用して、プログラムの複雑性を手懐けようとする設計手法は、今後もその必要性は変わらないと思う。

|

« astahとExcelの間でマインドマップをやり取りする方法はコピペだけ | トップページ | XPエクストリームプログラミングは偉大だ~時代がその設計思想に追いついた »

Java」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)


コメントは記事投稿者が公開するまで表示されません。



« astahとExcelの間でマインドマップをやり取りする方法はコピペだけ | トップページ | XPエクストリームプログラミングは偉大だ~時代がその設計思想に追いついた »