Locked History Actions

DI

DI

wikipediaの2011/7/10時点の翻訳(予定)。

オブジェクト指向コンピュータプログラミングにおける依存性注入(DI)とは、プログラムの部分が別の部分を使用することを示す、つまり外部依存や参照をソフトウェアコンポーネント供給するものである。 簡単に言えば、依存性注入とは、依存をソフトウェアコンポーネントに渡したり設定したりすることである。 技術的には振る舞いと依存解決とを分離することにより、強依存のコンポーネントを分割する。 コンポーネント自体が依存を要求する代わりに、それが与えられる、あるいは注入される。

開発者はコンポーネント間の依存を様々な理由で減らそうと努力するが、これは新たな問題を引き起こす。 コンポーネントは、その目的を達成するのに必要なほかのコンポーネントをどうやって知ることができるのか?

伝統的な解決策は依存をハードコードすることである。 例えば、データベースドライバが必要になると、コンポーネントは特定のドライバをロードするコードを実行し、それを設定し、データベースとのやりとりのために必要なメソッドを呼び出す。もし別のデータベースを扱う必要が出てくれば、このコードは変更されるか、あるいはもっと悪い場合はコピーされて変更される(これはDRY原則に反する)。

依存性注入がこれを解決する。 依存をハードコードする代わりに、単にコンポーネントは必要なサービスをリストし、DIフレームワークがそれを供給するというものである。

実行時に、独立コンポーネントがデータベースドライバをロードしセットアップし、データベースとの標準的な接続インターフェースを提供する。元のコンポーネントからは詳細は取り除かれ、新たな小さなデータベース特有のコンポーネントになり、複雑さが軽減される。 DIの擁護では、これらの新しいコンポーネントは「サービスコンポーネント」と呼ばれ、それらは一つあるいは複数の今ポーンンとに対してサービス(データベースアクセス)を提供する。

依存性注入は、「制御の反転」の特別な形態であり、必要な依存の取得プロセスが「反転された関心事」ということになる。 この用語はマーチンファウラーによって、メカニズムをより明確に表現するために提唱された。

目次

    1 Basics
    2 Code illustration using Java
        2.1 Highly coupled dependency
        2.2 Manually injected dependency
        2.3 Framework-managed dependency injection
    3 Benefits and drawbacks
    4 Types
    5 References
    6 See also
    7 External links

基本

DIが無い場合、コンシューマコンポーネントが、あるサービスを使って仕事を達成する場合に、そのインターフェースのみならず、特定の実装の詳細が必要になってしまう。 ユーザコンポーネントは、その使用だけではなく、サービスのライフサイクルも取り扱わねばならない。 つまり、インスタンス生成し、ストリームをオープンしたりクローズしたりし、不要なオブジェクトを廃棄したり。

DIを使用すると、サービスのライフサイクルは依存プロバイダによって行われることになる。 依存プロバイダは独立した外部コンポーネントであり、コンシューマコンポーネントと供給されるコンポーネントを結びつける役割を担う。 これにより、コンシューマは仕事を達成するために必要なサービス実装の参照のみが必要になる。

これらのパターンに必要なものは三つの要素である。

  • 依存コンシューマ
  • そのサービス依存の定義
  • インジェクタ(プロバイダやコンテナと呼ばれることもある)

依存者とはコンピュータプログラムにおいて仕事を達成する必要のあるコンピューマコンポーネントである。 そのためには、サブタスクを実行することのできる様々なサービス(依存先)の助けを必要とする。 プロバイダとは、依存者と依存先を協調して動作させることのできるコンポーネントであり、それらのライフサイクルも管理する。 プロバイダは、例えばサービスロケータとして、抽象ファクトリとして、あるいはフレームワークのようなもっと複雑な抽象化によって実現される。

次に例を示す。car(コンシューマ)が動くためには、engine(依存先)を必要とする。 carのengineはautomaker(依存プロバイダ)によって作成される。 carはengineを自身にインストールする方法は知らないが、動くためにはengineが必要である。 autmakeがengineをcarにインストールし、carは動くためにenineを用いる。

DIのコンセプトを用いれば、高レベルモジュールと低レベルサービスを分離することができる。 その結果はDIP(依存性逆転原則)と呼ばれるものになる。

Javaを使った例

上で説明したcar/engine例を使い、化にJavaによる例を示す。 これらは、マニュアルによる依存性注入とフレームワークによるものである。

public interface ICar {
    public float getSpeed();
    public void setPedalPressure(final float PEDAL_PRESSURE);
}
 
public interface IEngine {
    public float getEngineRotation();
    public void setFuelConsumptionRate(final float FUEL_FLOW);
}

強く結びついた依存

以下は、依存性注入を用いない一般的な例である。

public class DefaultEngineImpl implements IEngine {
    private float engineRotation = 0;
 
    public float getEngineRotation() {
        return engineRotation;
    }
 
    public void setFuelConsumptionRate(final float FUEL_FLOW) {
        engineRotation = …;
    }
}
 
public class DefaultCarImpl implements ICar {
    private IEngine engine = new DefaultEngineImpl();
 
    public float getSpeed() {
        return engine.getEngineRotation()*…;
    }
 
    public void setPedalPressure(final float PEDAL_PRESSURE) {
        engine.setFuelConsumptionRate(…);
    }
}
 
public class MyApplication {
    public static void main(String[] args) {
        ICar car = new DefaultCarImpl();
        car.setPedalPressure(5);
        float speed = car.getSpeed();
        System.out.println("Speed of the car is " + speed);
    }
}

In the above example, using the ICar interface, an engine instance is created by using the IEngine interface in order to perform operations on the car. Hence, it is considered highly-coupled, because it couples a car directly with a particular engine implementation.

In cases where the DefaultEngineImpl dependency is managed outside of the scope of the car, the class of the car implementing the ICar interface must not instantiate the DefaultEngineImpl dependency. Instead, that dependency is injected externally.

依存のマニュアル注入

Refactoring the above example to use manual injection:

public class DefaultCarImpl implements ICar {
    private IEngine engine;
 
    public DefaultCarImpl(final IEngine engineImpl) {
        engine = engineImpl;
    }
 
    public float getSpeed() {
        return engine.getEngineRotation()*…;
    }
 
    public void setPedalPressure(final float PEDAL_PRESSURE) {
        engine.setFuelConsumptionRate(…);
    }
}
 
public class CarFactory {
    public static ICar buildCar() {
        return new DefaultCarImpl(new DefaultEngineImpl());
    }
}
 
public class MyApplication {
    public static void main(String[] args) {
        ICar car = CarFactory.buildCar();
        car.setPedalPressure(5);
        float speed = car.getSpeed();
        System.out.println("Speed of the car is " + speed);
    }
}

In the example above, the CarFactory class assembles a car and an engine together by injecting a particular engine implementation into a car. This moves the dependency management from the DefaultCarImpl class into the CarFactory class. As a consequence, if the DefaultCarImpl needed to be assembled with a different DefaultEngineImpl implementation, the DefaultCarImpl code would not be changed.

In a more realistic software application, this may happen, if a new version of a base application is constructed with a different service implementation. Using factories, only the service code and the factory code would need to be modified, but not the code of the multiple users of the service. However, this still may not be enough abstraction for some applications, since in a realistic application there would be multiple factory classes to create and update.

フレームワーク管理された依存注入

There are several frameworks available that automate dependency management through delegation. Typically, this is done with a container using XML or metadata definitions. Refactoring the above example to use an external XML-definition framework:

    <service-point id="CarBuilderService">
        <invoke-factory>
            <construct class="Car">
                <service>DefaultCarImpl</service>
                <service>DefaultEngineImpl</service>
            </construct>
        </invoke-factory>
    </service-point>

/** Implementation not shown **/
 
public class MyApplication {
    public static void main(String[] args) {
        Service service = (Service)DependencyManager.get("CarBuilderService");
        ICar car = (ICar)service.getService(Car.class);
        car.setPedalPressure(5);
        float speed = car.getSpeed();
    }
}

In the above example, a dependency injection service is used to retrieve a CarBuilderService service. When a car is requested, the service returns an appropriate implementation for both the car and its engine.

As there are many ways to implement dependency injection, only a small subset of examples is shown herein. Dependencies can be registered, bound, located, externally injected, etc., by many different means. Hence, moving dependency management from one module to another can be accomplished in many ways.

メリットとデメリット

依存性注入アプローチの一つのメリットとしては、アプリケーションオブジェクト内のボイラープレートコード(決まりきったコード)を排除できることである。なぜなら、依存の初期化やセットアップはプロバイダコンポーネントによって行われるからである。

もう一つのメリットは構成のフレキシビリティがある。コードをリコンパイルせずに、あるサービスの別の実装を使用することができるようになるからである。これはユニットテストにおいて便利だ。コンフィギュレーションファイルを変更することにより、テスト対象のオブジェクトにサービスのフェイク実装を簡単に注入することができるのである。

しかし、それを必要とするオブジェクトから依存を取り去ることの、明確な理由がなければならないだろう。 一つのデメリットとしては、過度のあるいは不適切な依存性注入はアプリケーションを複雑にし、理解しにくくし、さらに変更を難しくする。依存性注入をしようするコードを何らかの魔術と感じる開発者もいるようだ。なぜなら、オブジェクトの生成と初期化がそれを使用するコードから完全に切り離されるからだ。この分離はその原因を突き止めることの困難な問題も引き起こす。

さらに、ある種の依存性注入フレームワークは冗長なコンフィギュレーションファイルを必要とし、それを変更するには、そのコンフィギュレーション自体とコードの双方を開発者が理解する必要がある。例えば、ウェブコンテナが二つの関連する依存によって初期化されるとする。そしてその一方の依存を必要とするユーザは、二つの関連に気づいていない場合がある。 ユーザは二つの関連に気づかないため、一方の依存を使用する際に、致命的な問題を引き起こすかもしれない。

もう一つのデメリットとしては、IDEにとっては「見えない」コンフィギュレーションのために、コードリファクタリングを正しく行えないかもしれない点である。ただし、ある種のIDEは様々なフレームワークの明示的なサポートを提供して、この問題を緩和している。

ある種のフレームワークは、リファクタリングを直接的に行うことができるよう、プログラミング言語それ自体でコンフィギュレーションできるようにしている。また、Grokウェブフレームワークのようなある種のフレームワークは、コードを調査しCoCを使うことにより、コンフィギュレーション情報を削減する方法を提供している。例えば、モデルとビュークラスが同一のモジュール内であれば、ビュー生成時には対応するモデルインスタンスがコンストラクタに渡される。

タイプ

ファウラーは依存提供方法のパターンによって、外部モジュールへの参照を取得する三つの方法を定義している。

  • Type 1 or interface injection, in which the exported module provides an interface that its users must implement in order to get the dependencies at runtime.
  • Type 2 or setter injection, in which the dependent module exposes a setter method that the framework uses to inject the dependency.
  • Type 3 or constructor injection, in which the dependencies are provided through the class constructor.

It is possible for other frameworks to have other types of injection, beyond those presented above.