依存性注入とサービスロケーターパターンの比較分析
Javaエコシステムにおいて、異なるプロジェクトから構成されるコンポーネントを統合するための軽量コンテナが登場しています。これらのコンテナは、一般的に「制御の反転」と呼ばれる概念に基づいており、実際には「依存性注入」というより具体的なパターンを採用しています。本稿では、このパターンの動作原理について詳細に検討し、サービスロケーターとの違いを比較します。
コンポーネントとサービスの概念
ソフトウェア要素の結合というテーマを考える上で、まず「サービス」と「コンポーネント」という用語の区別が必要になります。コンポーネントとは、作成者が制御しないアプリケーションによって変更なしに使用されることを意図したソフトウェア群です。一方、サービスはリモートインターフェースを通じて使用されるもので、同期または非同期的な手段(Webサービス、メッセージング、RPCなど)を含みます。
単純な実装例
特定監督者の映画リストを提供するコンポーネントを例として考えます。この機能は以下の単純なメソッドで実装されます:
public class MovieCatalog {
private MovieRepository repository;
public Movie[] findMoviesByDirector(String directorName) {
List<Movie> allMovies = repository.fetchAll();
Iterator<Movie> iterator = allMovies.iterator();
while (iterator.hasNext()) {
Movie currentMovie = iterator.next();
if (!currentMovie.getDirector().equals(directorName)) {
iterator.remove();
}
}
return allMovies.toArray(new Movie[allMovies.size()]);
}
}
この実装では、repositoryオブジェクトがすべての映画情報を返すことを前提としています。しかし、データストレージの実装が異なる場合、このクラスは柔軟性を持ちません。インターフェースによる抽象化を導入することでこの問題に対処できます:
public interface MovieRepository {
List<Movie> fetchAll();
}
現在の実装では、コンストラクタ内で具体的なクラスをインスタンス化しています:
public MovieCatalog() {
repository = new TextFileMovieRepository("movies.txt");
}
依存性注入の導入
依存性注入の基本的な考え方は、別のオブジェクト(アセンブラ)が適切な実装を注入することです。これにより、依存関係の図が変化します。
コンストラクタ注入
PicoContainerを使用したコンストラクタ注入の実装:
public class MovieCatalog {
private MovieRepository repository;
public MovieCatalog(MovieRepository repo) {
this.repository = repo;
}
}
public class TextFileMovieRepository implements MovieRepository {
private String fileName;
public TextFileMovieRepository(String fileName) {
this.fileName = fileName;
}
}
コンテナ設定コード:
private MutablePicoContainer setupContainer() {
MutablePicoContainer container = new DefaultPicoContainer();
Parameter[] repoParams = {new ConstantParameter("movies.txt")};
container.registerComponentImplementation(
MovieRepository.class,
TextFileMovieRepository.class,
repoParams
);
container.registerComponentImplementation(MovieCatalog.class);
return container;
}
セッター注入
Spring Frameworkにおけるセッター注入の実装:
public class MovieCatalog {
private MovieRepository repository;
public void setRepository(MovieRepository repo) {
this.repository = repo;
}
}
SpringのXML設定ファイル:
<beans>
<bean id="catalog" class="com.example.MovieCatalog">
<property name="repository">
<ref bean="movieRepo"/>
</property>
</bean>
<bean id="movieRepo" class="com.example.TextFileMovieRepository">
<property name="fileName">
<value>movies.txt</value>
</property>
</bean>
</beans>
インターフェース注入
インターフェース注入では、専用の注入インターフェースを定義します:
public interface RepositoryInjector {
void injectRepository(MovieRepository repo);
}
public class MovieCatalog implements RepositoryInjector {
private MovieRepository repository;
@Override
public void injectRepository(MovieRepository repo) {
this.repository = repo;
}
}
サービスロケーターの利用
依存性注入の代替として、サービスロケーターがあります。これはアプリケーションが必要とするすべてのサービスを取得できるオブジェクトです:
public class ServiceRegistry {
private static ServiceRegistry instance;
private MovieRepository movieRepository;
public static MovieRepository getMovieRepository() {
return instance.movieRepository;
}
public static void initialize(ServiceRegistry registry) {
instance = registry;
}
}
サービスロケーターを使用する場合、クライアントコードは以下のように記述されます:
public class MovieCatalog {
public Movie[] findMoviesByDirector(String director) {
MovieRepository repo = ServiceRegistry.getMovieRepository();
// 処理を続ける...
}
}
サービスロケーターと依存性注入の比較
両方のパターンは基本的なデカップリングを提供しますが、アプリケーションクラスに実装を提供する方法が異なります。サービスロケーターでは明示的にリクエストしますが、依存性注入ではサービスがクラスに現れます。
サービスロケーターは各ユーザーがロケーターへの依存性を持つ必要があります。ロケーターは他の実装の依存性を隠せますが、ロケーター自体への依存性は残ります。一方、依存性注入ではコンポーネントと注入器の間の依存性はありません。
コンストラクタ注入とセッター注入の選択
コンストラクタ注入はオブジェクト生成時の整合性を保証しますが、多くのパラメータがある場合や継承がある場合は複雑になることがあります。セッター注入は柔軟性がありますが、不完全な初期化状態を許容する可能性があります。
コードベースと設定ファイルの選択
多くのデプロイメントで使用されるアプリケーションでは、分離された設定ファイルが意味を持ちます。しかし、単純なアプリケーションではプログラムコードでの組み立ての方が明確な場合もあります。
設定と使用の分離
最も重要な原則は、サービスの設定と使用を分離することです。これはインターフェースと実装を分離する基本設計原則の適用です。異なるデプロイメントで異なる実装を選択できるようにすることで、コンポーネントの再利用性が向上します。