以下の文章は、Martin Fowler の「Inversion of Control Containers and the Dependency Injection pattern」を、かくたにが翻訳したものです。原著者の許可を得て翻訳・公開しています。
翻訳にあたっては、kdmsnr さんにご協力をいただきました。ありがとうございます。公開後の改訂履歴を記事の最後に記述しています。


Home Blog(ja) Articles Books About Me Contact Me ThoughtWorks

Inversion of Control コンテナと Dependency Injection パターン

マーチン・ファウラー

Java コミュニティでは軽量コンテナが花盛りである。 軽量コンテナは、異なるプロジェクトのコンポーネントをひとまとまりのアプリケーションとして組み立てることを支援する。 このようなコンテナの根底には、コンポーネントの結び付け方についての共通したパターンがある。 そのパターンのコンセプトは「Inversion of Control(制御の反転)」と、まことに包括的な名前で呼ばれている。 本記事では、このパターンの働きについて掘り下げることで、より具体的に「Dependency Injection(依存オブジェクト注入)」と命名し、 これを Service Locator の代替案として比較する。両者のいずれを選択するかはさしたる問題ではない。 「設定を利用から分離する」原則こそが重要なのだ。

主な最終更新日(原文): 2004/01/23

エンタープライズ Java の世界での楽しみのひとつは、メインストリームの J2EE 技術に対する代替案を構築しようとする活動が非常に多くみられることである。その多くがオープンソース界隈で起こっている。大抵は、重量級で複雑なメインストリームの J2EE 界に対する反動なのだが、それだけではない。別の選択肢の模索であったり、創造的なアイデアの提案であったりもする。 共通している取り組むべき懸念は、異なる要素をどのようにまとめあげるか、である。 たとえば、この Web コントローラのアーキテクチャと、背後に控えるデーターベースとをどのように結びつければよいのだろう?  それぞれは異なるチームが構築しているので、互いのやり取りはなるべく少なく済ませたいんだけど、といった場合だ。 多くのフレームワークは、この問題に対してスタブを採用している。 中には、属するレイヤの異なるコンポーネントを組み立てられる包括的な機能を提供し始めているものもある。 これらは軽量コンテナと呼ばれることが多く、たとえば PicoContainerSpring がそれに該当する。

これらのコンテナの根底にあるものは、数多くの興味深い設計原則であり、いずれも特定コンテナどころか Java プラットフォームにすら限定されないものである。 本記事では、これらの中からいくつかの原則を見ていくことからはじめようと思う。 サンプルには Java を利用するが、言及する原則のほとんどは、他のオブジェクト指向環境、とくに .NET にも適用できるだろう。

コンポーネントとサービス

要素をまとめあげるという話題を扱うや否や、サービスやコンポーネントといった用語にまつわるややこしい問題に直面する。 これらの用語の定義についての長くて論争じみた記事なら他所でたやすく見つけることができる。 ここでは本記事での目的のために、あちこちで使われている用語について私なりの定義を与える。

コンポーネントとはソフトウェアの塊である。コンポーネントは、変更されることなく利用されるものである。また、コンポーネントを利用するアプリケーションは、コンポーネント作成者の制御下にはないものとする。 「変更されることなく」とは、コンポーネントを利用するアプリケーションが、利用対象となるコンポーネントのソースコードを変更しないことを意味する。 とはいえ、コンポーネントの振る舞いは、コンポーネント作成者の許容する方法でならば、変更可能であっても構わないだろう。

サービスは、外部のアプリケーションに利用されるという意味ではコンポーネントに似ている。 目立った違いといえば、コンポーネントは内部的に利用されるのに対して(JARファイル、アセンブリ、DLL、あるいはソースコードのインポートと考えてほしい)、 サービスはリモートインターフェースを通じて遠隔的に利用されるということだ(例えば、Webサービス、メッセージングシステム、RPC、それにソケット)。

本記事では主に「サービス」を用いるが、同じ考え方をローカルコンポーネントにも適用できる。 もちろん、リモートサービスに簡単にアクセスできるようなローカルコンポーネントのフレームワークが必要となることもよくあるが、 だからといって「コンポーネントもしくはサービス」と書くのは読むのも書くのも疲れる。それに、今ドキはサービスと書いたほうがオシャレだしね。

素朴なサンプル

より具体的に理解できるように、実際に動くサンプルを利用しよう。サンプルはいつものように超単純だ。 短すぎて実用的ではないけれど、何をやっているのかを目で見てわかるには充分なものを使うつもりだ。 これなら実用的なサンプルの泥沼にハマることもない。

今回のサンプルでは、ある特定の監督による映画のリストを提供するコンポーネントを書こうと思う。 この超使える機能は、ひとつのメソッドで実装できる。

class MovieLister...
    public Movie[] moviesDirectedBy(String arg) {
        List allMovies = finder.findAll();
        for (Iterator it = allMovies.iterator(); it.hasNext();) {
            Movie movie = (Movie) it.next();
            if (!movie.getDirector().equals(arg)) it.remove();
        }
        return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
    }

この機能の実装は、素朴も素朴だ。処理としては、ファインダオブジェクト(便宜的にこう呼ぶ)に、 知っている総ての作品を返すように問い合わせる。そして、帰ってきたリストの中から特定の監督の作品を見つけだして、 作品の配列を呼び出し元に返す。この素朴な処理そのものを修正するつもりはない。 これはあくまで、本記事の意図するところを説明するための取っ掛かりでしかないのでね。

本記事の重要なポイントは、このファインダオブジェクトである。 もっといえば、MovieLister オブジェクトとファインダオブジェクトとをどうやって繋げるかである。 これがなぜポイントなのかというと、我が素晴らしき moviesDirectedBy メソッドを、 すべての映画リストがどうやって保存されているかということから完全に独立させておきたいからだ。 メソッドはファインダへの参照を持ちさえすればよく、ファインダは、findAll メソッドの 呼び出しに応じることができさえすればよい。これを明確にするために、、ファインダのインタフェースを定義する。

public interface MovieFinder {
    List findAll();
}

これで立派に疎結合された。だがしかし、どこかの時点で、実際に映画リストを扱う具象クラスを用意しなければならない。 ここでは、そのコードをリスト作成クラスのコンストラクタに書くことにする。

class MovieLister...
	private MovieFinder finder;
	public MovieLister() {
		finder = new ColonDelimitedMovieFinder("movies1.txt");
	}

実装クラスの名前は、映画リストがコロン区切りのテキストファイルになっていることに由来する。 実装の詳細はあなたのために取っておこう。ここで重要なのは、なにかしら実装が存在するのだ、ということなので。

さて。このクラスを使うのが私だけだとすれば、これでバッチリ、オトコマエだ。 けれど、もし友人がこの素晴らしいプログラムにひと目惚れして「コピーさせてもらえないかな」と言ってきたらどうしよう?  彼の映画リストもコロン区切りで、ファイル名も "movies1.txt"ならば、世はすべて事も無し。 たとえ映画リストのファイル名が違っていたとしても、ファイル名をプロパティファイルで指定できるようにするのは簡単だ。 しかし、映画リストの保存方法が根本的に違っていたらどうだろう。たとえば、SQLデータベースやXMLファイル、Webサービス、あるいは別フォーマットのテキストファイルだったら? こうなると異なったデータを扱うために別のクラスが必要だ。 そんなこともあろうかと、MovieFinder インタフェースは定義しておいた。 なので、moviesDirectedBy メソッドは変更しなくてもよいのだが、それだけでは充分ではない。 依然として、適切なファインダ実装を設定するために何らかの仕掛けが必要だ。

図 1: リスト作成クラスを単純に生成する場合の依存関係

1は現在の状況での依存関係を示している。 MovieLister クラスは、MovieFinder インターフェースと、インタフェース実装の両方に依存している。 インタフェースにだけ依存することが望ましいのだが、そうするとなると、インスタンスはどうやって生成すればよいのだろう?

拙著『P of EAA』では、こうした状況をプラグイン (Plugin)と呼んで取り上げている。 MovieFinder の実装クラスは、コンパイル時にはプログラムにリンクしないので、友人が何を使おうが勝手だ。 MovieLister をいかなる実装でも動作可能にするのではなく、後から実装をプラグインさせるようにする。 そうすれば私の手からは離れる。 問題は、どうすれば MovieLister クラスが MovieFinder の実装クラスを知らずとも構わないようにできるかだ。 それができたとしても、処理を実行するためには依然として、インタフェースを実装したインスタンスとの対話が必要になるわけだし。

これを実際のシステムに展開すれば、数十ものサービスやコンポーネントが必要になるだろう。 インターフェースを通じてそれぞれのコンポーネントを利用することで、抽象化を行うことはできる (インターフェースを考慮して設計されていなければアダプタを利用すればよい)。 しかし、システムを異なる方法でデプロイしたいのだとしたら、サービス間の相互作用をハンドルするためにプラグインを利用する必要がある。ここでプラグインを利用できれば、異なるデプロイには異なる実装を利用することができる。

では、中心となる問題は、どのようにしてプラグインを組み立ててアプリケーションとしてまとめあげるか、ということだろうか? 確かにこれは新種の軽量コンテナが取り組んでいる主要な問題のひとつであり、およそ軽量コンテナと呼ばれるものが皆、 「制御の反転(Inversion of Control: IoC)」を利用して解決しようとしている問題である。

制御の反転(Inversion of Control)

軽量コンテナがなぜ有用なのかというと、制御の反転を実装しているからだという。 しかしそれでは私にはなんのことやらさっぱりである。 制御の反転は、フレームワークに共通する特性なのだから「軽量コンテナはスゴイ。なんたって制御の反転を使っているからね」と言われても、それは「俺のクルマはスゴイ。なんたって車輪がついているからね」と言うようなものだ。

ここで疑問なのは、軽量コンテナは制御のどういった側面を反転させているのか、ということだ。 私がはじめて制御の反転というものに遭遇したとき、それはユーザインタフェースのメインループのことだった。 初期のユーザインターフェースは、アプリケーションプログラムで制御されていた。 「名前の入力」「住所の入力」みたいな一連のコマンドを取り扱いたいとなれば、 プログラムでプロンプトの表示と、それぞれの入力を制御する。 これがグラフィカルなUI(コンソールベースでもいいけど)になると、UIフレームワークにはメインループがあり、フレームワークからスクリーンの様ざまなフィールドの代わりとしてイベントハンドラが提供されている。プログラムではこのイベントハンドラを取り扱う。ここではプログラムの中心となる制御が反転されている。制御は個々のプログラムからフレームワークへと移されているのだ。

新種のコンテナにおいて反転されているのは、プラグイン実装のルックアップ方法である。 私の素朴なサンプルでいえば、MovieLister は MovieFinder の実装を直接インスタンス化することでルックアップしている。 これだと、ファインダはプラグインではなくなっている。 新種のコンテナが採用しているアプローチには、プラグインを利用するにあたって必ず従わなければならない取り決めが存在する。 この規約があることで、コンテナはMovieFinder 実装を MovieLister オブジェクトにインジェクト(inject: 注入)することが可能になる。

結論をいえば、このパターンにはもっと明確な名前が必要なように思う。 「制御の反転」という用語では包括的すぎる。これでは混乱する人が出てくるのも無理はない。 様ざまな IoC 支持者と多くの議論を重ねた末、その名前は Dependency Injection (依存オブジェクト注入)に落ち着いた。

手始めに Dependency Injection の様ざまな形式を見ていくとしよう。 その前にあらかじめ指摘しておきたいことがひとつ。Dependency Injection だけが、 依存性をアプリケーションクラスから取り除いてプラグイン実装へと移す唯一無二の方法ではない。 他のパターンを使っても同じことができる。Service Locator だ。 しかし、これについては後で議論することにしよう。先に Dependency Injection の説明を片づけたい。

Dependency Injection の形式

Dependency Injection の基本的な考え方は、独立したオブジェクトをAssembler(組み立て係)として用意し、 MovieFinder インタフェースの実装を MovieLister クラスのフィールドへ適切に設定させるというものだ。 依存関係は図2のようになる。

図 2: Dependency Injection での依存関係

Dependency Injection には主に3つの形式がある。それぞれに名前をつけよう。 コンストラクタ・インジェクション(Constructor Injection)、セッター・インジェクション(Setter Injection)、 インタフェース・インジェクション(Interface Injection)。 近頃の IoC に関する議論をご存知の読者諸賢ならば、Type 1 IoC(インタフェース・インジェクション)、 Type 2 IoC(セッター・インジェクション)、そして Type3 IoC(コンストラクタ・インジェクション)という呼び方を 聞いたことがあるだろう。だが、数字の名前というものは覚えづらい。本記事ではそれぞれを名前で呼ぶことにする。

PicoContainer でのコンストラクタ・インジェクション

インジェクションとはどのようなものかを示すのに、PicoContainer と呼ばれる軽量コンテナを使うことにしよう。最初にこのコンテナを取り上げるのは、ThoughtWorks での私の同僚が PicoContainer の開発に熱心だからだ(そう、ちょっとした依怙贔屓だね)。

PicoContainer は MovieFinder の実装を MovieLister クラスにインジェクトするにあたり、コンストラクタを利用する。 これを実現するために、MovieListerクラスでは、 インジェクトされるべきインスタンスを引数に取るコンストラクタを宣言する必要がある。

class MovieLister...
    public MovieLister(MovieFinder finder) {
        this.finder = finder;
    }

MovieFinder 自身も、PicoContainer に管理される。 したがってテキストファイルのファイル名もコンテナによってインジェクトされる。

class ColonMovieFinder...
    public ColonMovieFinder(String filename) {
        this.filename = filename;
    }

それから、PicoContainerはそれぞれのインタフェースがどの実装クラスと結び付けられるのかを通知してもらう必要がある。 MovieFinder にどういうファイル名がインジェクトされるのかについても同様だ。

    private MutablePicoContainer configureContainer() {
        MutablePicoContainer pico = new DefaultPicoContainer();
        Parameter[] finderParams =  {new ConstantParameter("movies1.txt")};
        pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);
        pico.registerComponentImplementation(MovieLister.class);
        return pico;
    }

この設定コードは、本来ならば別の設定クラスで記述されるべきものだ。 このサンプルでは、私の MovieLister クラスを利用する友人がそれぞれ、 適切な設定コードを彼ら自身でどこかのクラスに書くものとしよう。 もちろん、こうした設定情報を設定ファイルへと分離することもよくある。 PicoContainer 自身には、そうした機能は含まれていないが、NanoContainer という密接に関連したプロジェクトが、 XML設定ファイルを書けるようにするためのしかるべきラッパーを提供している。 NanoContainer が XML をパースし、PicoContainer に必要な設定を行う、といった具合だ。 NanoContainer の哲学は、設定ファイルのフォーマットをコンテナのメカニズムから分離することである。

PicoContainer を利用するためには、以下のようなコードを書く。

    public void testWithPico() {
        MutablePicoContainer pico = configureContainer();
        MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);
        Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
        assertEquals("Once Upon a Time in the West", movies[0].getTitle());
    }

なお、このサンプルではコンストラクタ・インジェクションを利用しているが、 PicoContainer ではセッター・インジェクションもサポートしている (開発者たちはコンストラクタ・インジェクションのほうが好みのようだけれど)。

Spring でのセッター・インジェクション

Spring Framework は エンタープライズ Java 開発向けの守備範囲の広いフレームワークだ。 トランザクション、永続化フレームワーク、Web アプリケーション開発や JDBC に関する抽象レイヤがある。 PicoContainer と同じく、Spring Framework もコンストラクタ・インジェクションとセッター・インジェクションとをサポートしているが、Spring の開発者たちの好みは、セッター・インジェクションのようだ――なので、ここでサンプルとするのに丁度良い。

MovieLister がインジェクションに対応できるように、 サービス設定用の setter メソッドを定義しなければならない。

class MovieLister...
    private MovieFinder finder;
	public void setFinder(MovieFinder finder) {
		this.finder = finder;
	}

同様に、MovieFinder には文字列の setter を定義する。

class ColonMovieFinder...
    public void setFilename(String filename) {
        this.filename = filename;
    }

3番目のステップとして、ファイルに設定を記述する。Spring での設定は XML ファイルでもコードでも可能だが、 XMLで行うことが望ましいとされている。

    <beans>
        <bean id="MovieLister" class="spring.MovieLister">
            <property name="finder">
                <ref local="MovieFinder"/>
            </property>
        </bean>
        <bean id="MovieFinder" class="spring.ColonMovieFinder">
            <property name="filename">
                <value>movies1.txt</value>
            </property>
        </bean>
    </beans>

テストはこんな感じだ。

    public void testWithSpring() throws Exception {
        ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");
        MovieLister lister = (MovieLister) ctx.getBean("MovieLister");
        Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
        assertEquals("Once Upon a Time in the West", movies[0].getTitle());
    }

インタフェース・インジェクション

3番目のインジェクション技法は、インジェクション用のインタフェースを定義して利用する方法だ。 Avalon はこの方法を採用しているフレームワークである。 Avalonについては後でもう少し詳しく取り上げるつもりなので、ここでは簡単なサンプルコードを使って説明したい。

この方法ではまず、インジェクションを行うためのインタフェース (インジェクション・インタフェース)を定義することからはじめる。 以下は、MovieFinder をオブジェクトにインジェクトするためのインタフェースである。

public interface InjectFinder {
    void injectFinder(MovieFinder finder);
}

このインタフェースは MovieFinder インタフェースを提供するあらゆるクラスのために定義される。 MovieFinder を利用したいクラスは必ず InjectFinder インタフェースを実装しなければならない。 ここでは MovieLister がそれに該当する。

class MovieLister implements InjectFinder...
    public void injectFinder(MovieFinder finder) {
        this.finder = finder;
    }

ファイル名を MovieFinder の実装にインジェクトするために、同様のアプローチをとる。

public interface InjectFinderFilename {
    void injectFilename (String filename);
}
class ColonMovieFinder implements MovieFinder, InjectFinderFilename......
    public void injectFilename(String filename) {
        this.filename = filename;
    }

そうしたら、毎度おなじみの実装をまとめあげる設定を書く。 簡単にするために、ここではコードで設定する。

class Tester...
    private Container container;

     private void configureContainer() {
     	container = new Container();
     	registerComponents();
     	registerInjectors();
     	container.start();
    }

この設定には、2つのステージがある。コンポーネントの登録にルックアップ用のキーを利用する箇所は、他のサンプルとよく似ている。

class Tester...
	private void registerComponents() {
		container.registerComponent("MovieLister", MovieLister.class);
		container.registerComponent("MovieFinder", ColonMovieFinder.class);
	}

目新しい手順はインジェクタの登録だ。インジェクタは、依存するコンポーネントをインジェクトする。 それぞれのインジェクション・インタフェースには、依存するオブジェクトをインジェクトするためのコードが必要だ。 ここでは、インジェクタ・オブジェクトをコンテナに登録することでそれが行えるとしよう。 それぞれのインジェクタ・オブジェクトがインジェクタ・インタフェースを実装しているのだ。

class Tester...
	private void registerInjectors() {
		container.registerInjector(InjectFinder.class, container.lookup("MovieFinder"));
		container.registerInjector(InjectFinderFilename.class, new FinderFilenameInjector());
	}
public interface Injector {
	public void inject(Object target);

}

依存関係が、このコンテナ向けに書かれたクラスに対するものであるときには、 コンポーネント自身がインジェクタ・インタフェースを実装することも意味がある。 MovieFinder で示した例がそうだ。 文字列のような一般的なクラスの場合には、私なら設定コード内でインナークラスを定義して使う。

class ColonMovieFinder implements Injector......
    public void inject(Object target) {
	    ((InjectFinder) target).injectFinder(this);
    }
class Tester...
    public static class FinderFilenameInjector implements Injector {
        public void inject(Object target) {
            ((InjectFinderFilename)target).injectFilename("movies1.txt");
        }
    }

テストでもコンテナを利用する。

class FinderFilenameInjector...
    public void testIface() {
        configureContainer();
        MovieLister lister = (MovieLister)container.lookup("MovieLister");
        Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
        assertEquals("Once Upon a Time in the West", movies[0].getTitle());
    }

コンテナは、定義されたインジェクション・インタフェースを依存関係の把握に利用し、 インジェクタを依存関係の正しいインジェクトに利用する。 (ここでは特定のコンテナ実装は問題ではない。書いたところで笑われるだけなので見せてあげないよ)

Service Locator を利用する

Dependency Injection の嬉しさのポイントは、依存性を取り除けることである。 たとえば、MovieLister クラスから MovieFinder を実装した具象クラスへの依存性である。 このおかげで、私は友人たちに MovieLister を利用してもらえるし、彼らは彼らで、自分たちの環境に合うような実装をプラグインすることができる。しかしながら、Dependency Injection だけがこうした依存性を取り除く唯一無二の方法ではない。 Service Locator を利用してもよい。

Service Locator の背後にある基本的な考え方は、あるアプリケーションが必要とするであろうサービスのすべてを保持する、単一のオブジェクトを用意するというものだ。したがって、今回のアプリケーション用 ServiceLocator は、必要に応じて MovieFinder を返すメソッドを持つことになる。そうなると当然、MovieLister から ServiceLocator への参照が発生してしまい、結果として図3のような依存関係を示すことになる。

図 3: Service Locatorでの依存関係

この場合、ServiceLocator はシングルトン(Singleton)なレジストリ(Registry)として利用することになる。 MovieLister は(インスタンス化された後であれば) MovieFinder を取得して利用することができる。

class MovieLister...
    MovieFinder finder = ServiceLocator.movieFinder();
class ServiceLocator...
    public static MovieFinder movieFinder() {
        return soleInstance.movieFinder;
    }
    private static ServiceLocator soleInstance;
    private MovieFinder movieFinder;

Dependency Injection を利用する場合と同じように、Service Locator でも設定が必要だ。 ここでは、設定をコードによって行うが、設定ファイルから適切なデータを読み込んで利用することも 難しくない。

class Tester...
    private void configure() {
        ServiceLocator.load(new ServiceLocator(new ColonMovieFinder("movies1.txt")));
    }
class ServiceLocator...
    public static void load(ServiceLocator arg) {
        soleInstance = arg;
    }

    public ServiceLocator(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

テストコードはこうだ。

class Tester...
    public void testSimple() {
        configure();
        MovieLister lister = new MovieLister();
        Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
        assertEquals("Once Upon a Time in the West", movies[0].getTitle());
    }

こうした ServiceLocator は実装を置き換えられず、テストできないので良くない、という不満をよく聞く。 確かに、このテのトラブルが発生するようなマズイ設計をすることはできる。だが、できるからといってそうすべきではないだろう。 たとえば、私のサンプルでは ServiceLocator クラスのインスタンスは単なるデータホルダでしかない。 ここで各サービス用に ServiceLocator のテスト実装を作成することは簡単にできるだろう。

より洗練された ServiceLocator を用意するために、ServiceLocator クラスをサブクラス化して、 そのサブクラスをレジストリのクラス変数に渡すこともできる。 static メソッドではなく、インスタンスメソッドを呼ぶように変更することもできる。 そのほうがインスタンス変数へ直接アクセスするよりも好ましい。 スレッド毎のサービス保管場所を用意すれば、スレッド固有のロケータを用意することもできる。 いま述べたことはすべて、ServiceLocator クラスのクライアントコードを変更することなしに実現可能だ。

ここからわかることはつまり、ServiceLocator はレジストリではあるが、シングルトンではないということだ。 シングルトンはレジストリの簡単な実装法のひとつであり、その実装判断を変えることは簡単である。

Service Locator に隔離されたインタフェースを利用する

上述のシンプルなアプローチで困ることのひとつは、MovieLister は1つのサービスしか利用しないのもかかわらず、 全サービスを提供する Service Locator クラスに依存してしまうことだ。 この依存は隔離されたインタフェース(Segregated Interface)を利用して減らすことができる。 この方法なら、フルサイズの ServiceLocator インタフェースを利用する代わりに MovieLister で最低限必要となるインタフェースだけを宣言すればよい。

この場合、MovieLister のプロバイダは、MovieFinder を保持するロケータを取得するインタフェースも提供しなければならない。

public interface MovieFinderLocator {
    public MovieFinder movieFinder();

次に、ServiceLocator はこのインタフェースを実装して、MovieFinder へアクセスできるようにする必要がある。

    MovieFinderLocator locator = ServiceLocator.locator();
    MovieFinder finder = locator.movieFinder();
   public static ServiceLocator locator() {
        return soleInstance;
    }
    public MovieFinder movieFinder() {
        return movieFinder;
    }
    private static ServiceLocator soleInstance;
    private MovieFinder movieFinder;

気をつけて欲しいことがある。我われはインタフェースを利用したいのだから、もはや static メソッドを経由してサービスにアクセスしてはいけない。ロケータのインスタンスを取得するためにはクラスを利用すべきであり、そうして取得したインスタンスを通じて利用したいサービスにアクセスするのだ。

動的 Service Locator

上述のサンプルは静的である。そのため、 Service Locator クラスは必要なサービスごとにメソッドを用意している。 しかしこれが唯一の方法ではない。動的 Service Locator を作成してもよい。 動的 Service Locator では必要なあらゆるサービスを隠蔽することができるし、どのサービスを利用するかも実行時に選択できる。

ここでは、Service Locator はサービスごとにフィールドを持つ代わりに、マップを利用する。 また、サービスの取得と読み込みのために総称的なメソッドを用意する。

class ServiceLocator...
    private static ServiceLocator soleInstance;
    public static void load(ServiceLocator arg) {
        soleInstance = arg;
    }
    private Map services = new HashMap();
    public static Object getService(String key){
        return soleInstance.services.get(key);
    }
    public void loadService (String key, Object service) {
        services.put(key, service);
    }

設定には、適切なキーによるサービスの読み込みも含める。

class Tester...
    private void configure() {
        ServiceLocator locator = new ServiceLocator();
        locator.loadService("MovieFinder", new ColonMovieFinder("movies1.txt"));
        ServiceLocator.load(locator);
    }

サービスを利用するためには、キーと同じ文字列を渡す。

class MovieLister...
    MovieFinder finder = (MovieFinder) ServiceLocator.getService("MovieFinder");

総合的に考えると、私はこのアプローチが気に入らない。 確かに柔軟ではある。けれど、明示的であるとは言いがたい。 サービスを探し出すためには、文字列のキーを利用するしかない。 それよりは明示的なメソッドを利用するほうが好ましいと思う。 インタフェースの定義を見れば、サービスがどこで利用されているのがわかるからだ。

Avalon で Service Locator と Dependency Injection の両方を利用する

Dependency Injection と Service Locator は必ずしも相互排他的なコンセプトであるというわけではない。 両者を一緒に利用している良いサンプルに、Avalon フレームワークがある。 Avalon は Service Locator を利用するだけでなく、インジェクションを利用して、 ロケータがどこにあるのかをコンポーネントに通知している。

Berin Loritsch は私のサンプルの、Avalon を利用したバージョンを送ってきてくれた。

public class MyMovieLister implements MovieLister, Serviceable {
    private MovieFinder finder;

    public void service( ServiceManager manager ) throws ServiceException {
        finder = (MovieFinder)manager.lookup("finder");
    }

service メソッドはインタフェース・インジェクションのサンプルであり、 コンテナが MyMovieLister に ServiceManager をインジェクトできるようにしている。 このサンプルでは、MovieLister は ServiceManager をフィールドに保持しないで、 ServiceManager からすぐに MovieFinder をルックアップして利用している。

どの選択肢を採用するかを決定する

ここまでで、パターンとそれぞれのバリエーションを私がどう理解しているか、集中して説明してきた。 これで、メリット・デメリットについて議論する準備が整った。どういった場合にどちらを利用すべきかを明確にするとしよう。

Service Locator 対 Dependency Injection

第一の選択は、Service Locator と Dependency Injection とのいずれを選ぶかである。 まずポイントとなるのは、どちらの実装も疎結合を基本としている点だ (今回の素朴なサンプルに見てとることはできないが)。 いずれを採用しても、アプリケーションコードは具体的なサービス・インタフェースの実装に依存しない。 2つのパターンの間に見られる重要な違いは、 それぞれのサービス実装がアプリケーションクラスへと提供される方法である。 Service Locator では、アプリケーションクラスが明示的にロケータへメッセージを送る。 Dependency Injectionでは、明示的なリクエストを発行することなしにサービスがアプリケーションクラスに現れる――であるからこそ、制御の反転と呼ばれるわけだが。

制御の反転はフレームワークに共通する機能であるが、その機能にはそれなりの代償も伴う。 制御の反転は理解しづらくなりがちである。デバッグしようとしたときに問題となることもある。 よって、総合的な見地から、私は制御の反転については、どうしても必要となるまで利用を避けたい。 これは Dependency Injection が使えない、と言っているわけではない。 単に、より素直な代替案があるのなら、それを利用するのはもっともだと言いたいだけである。

ここでの重要なポイントは、Service Locator では、サービスの利用者すべてがロケータへ依存してしまうことだ。 ロケータはサービス実装への依存を隠蔽するが、サービス利用者はロケータそのものには依存せざるをえない。 Service Locator と Dependency Injection とのどちらを採用するかの判断は、 ロケータへの依存性が問題になるかどうかにかかっている。

Dependency Injection では、コンポーネントの依存性がどうなっているかを簡単に理解できる。 インジェクションの仕組み(たとえばコンストラクタ)を見るだけでよい。 一方、Service Locator では、ロケータを呼び出している箇所をソースコードから検索しなければならない。 近頃のIDEは参照検索機能が充実しているが、だからといってコンストラクタやセッターメソッドを見るだけでよいという程には簡単ではない。

アプリケーションクラスからロケータへの依存が問題になるかどうかは多分に、サービス利用者の性質による。 単一のサービスを様ざまなクラスが利用するようなアプリケーションを構築しているのであれば、 この依存は大した問題ではない。MovieLister を友人に提供する、という私の例ならば、Service Locator を使ってもうまく動く。 友人たちは、ロケータが適切なサービス実装を引き出せるように設定するだけでよい。 設定を行う手段は、コードでもファイルでも、どちらでもよい。 このシナリオでは、Dependency Injection を利用した制御の反転が何者にも代えがたいほど魅力的かどうかはわからない。

ところが、MovieLister が知らない人達の作るアプリケーション向けコンポーネントだとすると、話は違ってくる。 私にはコンポーネント利用者の使うであろう Service Locator の API はわからない。 利用者各々の間で互換性のない Service Locator を利用するかもしれない。 こういった事態に対しては、隔離されたインタフェースを利用することでやり過ごすこともできるだろう。 利用者たちがそれぞれ、私のインタフェースを彼らのロケータに合わせるべくアダプタを書くこともできるけれど、 どうしたって結局私は、自分の作成したインタフェースをルックアップする、最初のロケータについては知る必要があるのだ。 それに、アダプタが使われはじめると「コンポーネントはロケータと直接つながっている」というシンプルさが失われてしまう。

その点、Dependency Injection では、コンポーネントはコンテナに依存しなくなる。だがしかし、 一旦設定が完了してしまうと、それ以上のサービスをコンテナから取得できなくなってしまう。

Dependency Injection を憎からず思う人びとが共通して挙げる支持理由は「Dependency Injection はテストを簡単にする」である。 ここでのポイントは、テストを行うためには、実際のサービス実装をスタブあるいはモックへと簡単に置き換えられることが必須である、ということだ。しかしながら、この点において Dependency Injection と Service Locator との間に本質的な違いは存在しない――いずれもすんなりスタブ化できるのだ。思うに、サービスが簡単にスタブ化できないという考えは、プロジェクトでそうできるような努力をしていないことから出てくるのではなかろうか。「簡単にスタブ化してテストするなんてできないよ」というのは、あなたの設計が深刻な問題を抱えていることを暗に示しているのでは?

もちろんテストの問題は、Java の EJB フレームワークのようなお節介この上ないコンポーネント環境によって悪化させられている。 このテのフレームワークは、アプリケーションコードへの影響を最小化すべきだと思う。とりわけ、編集・実行・編集・実行のサイクルをスピードダウンさせるべきではない。重量級コンテナの代わりにプラグインを利用すれば、その点では大いに有用である。これはテスト駆動開発のようなプラクティスの核心でもある。

結局、「誰にとって問題なのか」というと、自分の書いたコードが自分の制御下にはないアプリケーションで利用されるコード作成者にとって問題なのである。このような場合には、Service Locator の最低限の前提でさえも問題となる。

コンストラクタ 対 セッター・インジェクション

サービスを組み合わせるためには、何らかの取り決めが必須である。この規約に従ってそれぞれの要素をまとめあげるのだ。 まずもって Dependency Injection が有利な点は、とてもシンプルな規約があればよいということだ。 コンストラクタ・インジェクションにするかセッター・インジェクションにするかぐらいを決めればよい。 コンポーネントに対しては何ら変わったことをする必要はない。 コンテナにすべてを設定させるわけだが、それにしたってとても単純なものだ。

ところが、インタフェース・インジェクションは他に比べると「侵略的」である。 すべてを明確にするために数多くのインタフェースを書かねばならない。 コンテナが必要とするインタフェース一式が少なければ悪くないとは思う(Avalon で採用している方法がそうだ)。 それでも、コンポーネントや依存性を組み立てるためには多くの作業が必要となる。 この作業量こそが、近頃の軽量コンテナが解決しようとしている問題であり、 セッターとコンストラクタによるインジェクションを採用している理由なのだが。

セッター・インジェクションとコンストラクタ・インジェクションのどちらを選択するかという問題で興味深いのは、オブジェクト指向プログラミング全般に関する問題がそっくりそのまま当てはまることだ――フィールドに値を設定する際には、コンストラクタで行うべきか、それともセッターで行うべきか?

私は長きにわたる習慣から、オブジェクトは可能な限り、コンストラクタが呼び出された時点で妥当な状態となるべく生成している。 この教えは『ケント・ベックのSmalltalkベストプラクティス・パターン――シンプル・デザインへの宝石集――』に遡る。すなわち、コンストラクタ・メソッド(Constructor Method)とコンストラクタ・パラメータ・メソッド(Constructor Parameter Method)だ。 パラメータ付きのコンストラクタは、妥当なオブジェクトを生成するには何が必要なのかを明確にできる、またとない場所である。 妥当なオブジェクトを生成する方法が複数あるのならば、複数のコンストラクタを定義して異なる組み合わせを明示するのだ。

コンストラクタによる初期化の別の利点は、セッターを提供しないので、イミュータブル(immutable:変更不能)なフィールドをすべて、きちんと隠蔽できるということだ。これは重要だと思う――変更すべきでないものがあれば、そこにはセッターが存在しないのだ。コミュニケーションも簡単になる。一方、セッターによる初期化では、こうしたコミュニケーションは苦痛となるだろう (実際にこうした状況に遭遇したときは、setter 命名規約を使わないで、 initFoo メソッドのような命名規約を採用する。 こうすれば、オブジェクト生成時にしか呼び出してはいけないかのような プレッシャーを与えることはできる)。

とはいえ、何事にも例外が存在する。 わけがわからなくなるぐらいにコンストラクタのパラメータが多くなることがある (特に、名前付きパラメータを利用できない言語では)。 なるほど長いコンストラクタというのは「忙し過ぎ」オブジェクトの兆候であることがままある。 よって一般的にはオブジェクトを分割すべきである。だがしかし、長いコンストラクタを利用したい場合だってある。

また、妥当なオブジェクトを生成する手段がいくつかあるのだが、コンストラクタで明示するのは難しい場合もある( コンストラクタのバリエーションをパラメータの数と型でしか表せない言語とか)。 こういうときこそ、ファクトリ・メソッド(Factory Method)の出番である。 プライベートなコンストラクタとセッターを組み合わせて、本来コンストラクタでやるべき作業を行うことができる。 しかし困るのは、コンポーネントを組み立てるために古典的なファクトリ・メソッドを使うと、普通は static メソッドになってしまうので、インタフェースが使えなくなることだ。 ファクトリをクラスにもできるが、そうすると結局、単にファクトリがもうひとつのサービスインスタンスになってしまうだけだ。 ファクトリサービスは往々にして役立つ戦法ではあるけれど、どうしたって今述べたテクニックのどれかを使って、ファクトリをインスタンス化しなければならない。

コンストラクタのパラメータに、文字列みたく単純な型を使ってしまうとこれまた悩ましい。 セッター・インジェクションであれば、それぞれのセッターに名前をつけられるので、 セッターの受け取る文字列が何に使われるのかを明示することができる。 しかしコンストラクタを利用するとなると、引数となる文字列の順番に頼るほかない。 これにきちんと従うのは至難の業だ。

複数のコンストラクタを持つクラスと継承関係があると、事態はますますややこしくなる。 つつがなくすべてを初期化するために、スーパークラスのコンストラクタといちいち対応させる必要があるからだ。 サブクラスで独自に引数を追加した場合も同様である。こうなるとコンストラクタ爆発とでも呼ぶべき事態を引き起こすだろう。

とまあ、幾つも不利な点があるのだが、それでも私はコンストラクタ・インジェクションから始めることをオススメする。 といっても、ここまででまとめてきたようなことが問題となりはじめたときに備えて、 すぐにセッター・インジェクションへと切り替えられるようにしておくのがよいだろう。

コンストラクタ・インジェクションかそれともセッター・インジェクションかという問題は、 フレームワークの機能としてDependency Injection を提供するチーム間に多くの議論を呼んだ。 しかしながら、こうしたフレームワークを構築する人びとのほとんどは、両方のメカニズムを サポートすることが重要だと認識しているようである。チームとしてはいずれか一方が好みであったとしても、だ。

コードか設定ファイルか

本来は独立しているのだが、しばしば一緒くたにされてしまう議論がある。 サービスの組み合わせをまとめあげるためには、設定ファイルを利用すべきか、それとも API を使ったコードを利用すべきか? といった話だ。大抵のアプリケーションは複数の環境にデプロイされるだろうから、通常は設定ファイルを分離しておくのは道理にかなうことになる。その場合は必ずといってよいほど、XML が使われることになるだろうし、それも納得だ。 とはいえ、コードでプログラミングするほうが簡単な場合もある。ひとつ例をあげるならば、単純なアプリケーションで、デプロイのバリエーションが多くないケースだ。この場合は、コードは少ないし、分離された XML ファイルよりも見通しがよいだろう。

対照的なケースは、組み立て方がとても複雑で、条件分岐が存在する場合だ。 設定が XML というよりもプログラミング言語に近くなりだすと、やがては行き詰まる。 こうなると、通常のプログラミング言語を利用して分かりやすいコードを書いたほうが良くなる。 そのときには、コンポーネントを組み立てるビルダークラスを書こう。 複数の構築シナリオが必要であれば、いくつかのビルダークラスを用意し、 単純な設定ファイルを使って適切な実装を選択するのだ。

思うに、人びとは設定ファイルに色いろ定義しようと欲張りすぎである。 プログラミング言語を使えば、設定メカニズムを素直でパワフルなものに出来ることが多い。 今どきの言語であれば、大規模システム向けのプラグインを組み立てる細ごまとしたアセンブラを簡単にコンパイルできる。 コンパイルが面倒くさいのであれば、スクリプト言語でも同じことを巧くやれるだろう。

「設定ファイルは非プログラマによって編集されるのだから、プログラミング言語を使ってはいけない」 ともよく言われる。だが、そんなケースはどれだけ頻繁に発生するのだろうか? 本当に、非プログラマに複雑なサーバサイドアプリケーションのトランザクション隔離レベルを変更させたいと思っているのだろうか? 非プログラミング言語による設定ファイルは、それが単純な限りにおいては、巧くいくだろう。だが、それが複雑になったならば、きちんとしたプログラミング言語を使うことを考慮したほうがよい。

近頃の Java 界でよく見かけるものに、設定ファイルの不協和音とでも呼ぶべき現象がある。 すべてのコンポーネントにそれぞれ設定ファイルがあって、それぞれが異なるやり方で設定されている。 1ダースのコンポーネントを利用していたとすると、 あっというまに1ダースの設定ファイルの内容を同期させていくことになりかねない。

ここで私からのアドバイス。すべての設定をプログラミングから簡単に行えるようなインタフェースを必ず用意すること。 設定ファイルを分離するのは、オプション機能でよい。設定ファイルをハンドリングして、プログラミング用のインタフェースを利用する機能は簡単に構築できる。 コンポーネントを作成している際には、設定方法の選択は気にしない。プログラミング用のインタフェースを使うのか、あなたの決めた設定ファイルフォーマットを使うのか、あるいは彼ら独自の設定ファイルをプログラミング用インタフェース用に変換させて使うのか。どうするかはコンポーネントのユーザ自身に決めさせればよいのだ。

設定を利用から分離する

何よりも重要なのは、サービスの設定をその利用から分離することの徹底である。 事実これは基本的な設計原則である、「インタフェースを実装から分離する」原則に隣り合うものだ。 よくある例でいえば、オブジェクト指向プログラミングでは、どのクラスをインスタンス化すべきかの条件判断は、 重複した条件分岐の記述よりもポリモフィズムを利用した遅延評価で実現する、ってやつだ。

設定と利用の分離が、単一のコードベース内においてでさえ有効なのであれば、 コンポーネントやサービスのような外部要素を利用する場合には、欠かせないものとなるだろう。 まず考慮すべきは、特定環境にデプロイするまで実装クラスの選択を延期したいかどうかである。 その必要がある場合は、プラグインの実装を利用すればよい。 プラグインを利用するのであれば、プラグインの組み立てはアプリケーションの残りの部分から分離させておく必要がある。 デプロイごとに異なる設定に置き換えることを簡単にするためだ。 どのようにしてこれを実現するのかは、二の次だ。この設定メカニズムは、Service Locator でも Dependency Injection でも実現可能なのだから。

さらなる論点

本記事では、Dependency Injection と Service Locator を利用したサービス設定の基本的な問題に集中してきた。 注目に値する論点が他にもいくつかあるのだが、今はそれを掘り下げる時間がない。 特に取り上げたいのは、ライフサイクルの振る舞いだ。 コンポーネントのなかには、独立したライフサイクルイベントが必要なものもあるだろう(たとえば、サービスの停止や開始)。 これ以外にも、アスペクト指向の考え方を軽量コンテナに採り入れることへの関心の高まりがある。 今のところ、本記事ではアスペクト指向については考慮していないが、今後追加するなり、他の記事を書くなりして、 是非とも取り組んでみたいと思っている。

より多くの議論や進んだアイデアについては、軽量コンテナを提供しているウェブサイトを訪れるとよい。 PicoContainerSpring のウェブサイトがその取っ掛かりになるだろう。

まとめ

近ごろ急増している軽量コンテナのすべてが、共通して基礎としているパターンは、どのようにしてサービスの組み立てを行うかについてのパターン――Dependency Injection パターンである。 Dependency Injection は Service Locator の代替案として有効である。 アプリケーションクラスを構築するにあたっては、両者ともだいたい同じようなものだが、私は Service Locator のほうが少し優勢だと思う。こちらのほうが振る舞いが素直だからだ。しかしながら、構築したクラスが複数のアプリケーションで利用されるのであれば、Dependency Injection のほうがより良い選択肢となる。

Dependency Injection を採用した場合は、さらにそのスタイルを選択しなければならない。 私のオススメはコンストラクタ・インジェクションだ。通常はこちらを利用し、 コンストラクタ・インジェクションならではの問題に陥ったらセッター・インジェクションへと切り替えるのだ。 コンテナを新規に構築する場合や、既存コンテナの採用を考える場合には、 コンストラクタ・インジェクションとセッター・インジェクションの両者をサポートできるものを 構築する(または採用する)ことを心がけよう。

Service Locator と Dependency Injection とのどちらを選ぶかは大した問題ではない。 サービスの設定をアプリケーション内でのサービス利用から分離するという原則こそが重要なのだ。

謝辞

本記事の執筆にあたって助力いただいた多くの方がたに心から感謝する。 Rod Johnson、Paul Hammant、Joe Walnes、Aslak Hellesøy、Jon Tirsén そして Bill Caputo は 今回取り上げたコンセプトを私が理解することを手助けしてくれた。 また、草稿に対するコメントも寄せてくれた。 Berin Loritsch と Hamilton Verissimo de Oliveira は Avalon を巧く使うために、 とても役立つアドバイスを寄せてくれた。 Dave W Smith は、初期のインタフェース・インジェクションの設定コードに対して 一貫して強固な意見を述べてくれた。おかげで、私は自分の愚かさと向き合うことができた。

主な改訂点

2004/01/23: インタフェース・インジェクションの設定コードを書き直した。

2004/01/16: Avalon で Service Locator と Dependency Injection の両方を利用した短いサンプルを追加。

2004/01/14: 初版公開。



© Copyright
Martin Fowler, all rights reserved
日本語訳: かくたに
翻訳版の改訂履歴: