Revision 5 as of 2011-07-18 00:38:42

Clear message
Locked History Actions

DI

DI

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

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

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

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

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

At runtime, an independent component will load and configure the database driver and offer a standard interface to interact with the database. Again, the details have been moved from the original component to a set of new, small, database-specific components, reducing the complexity of them all. In DI terms, these new components are called "service components", as they render a service (database access) for one or more other components.

Dependency injection is a specific form of inversion of control, where the concern being inverted is the process of obtaining the needed dependency. The term was first coined by Martin Fowler to describe the mechanism more clearly.[1]

目次

    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

基本

Without dependency injection, a consumer component that needs a particular service in order to accomplish a task will depend not only on the interface of the service, but on the details of a particular implementation of it as well. The user component has to handle both its use and the life cycle of that service - creating an instance, opening and closing streams, disposing of unneeded objects, etc.

Using dependency injection, however, the life-cycle of a service is handled by a dependency provider rather than the consumer. The dependency provider is an independent, external component that links the consuming component and the providing component. The consumer would thus only need a reference to an implementation of the service that it needed in order to accomplish the necessary task.

Such a pattern involves at least three elements:

  • a dependent consumer, the definition of its service dependencies, and an injector (sometimes referred to as a provider or container).

The dependent is a consumer component that needs to accomplish a task in a computer program. In order to do so, it needs the help of various services (the dependencies) that execute certain sub-tasks. The provider is the component that is able to compose the dependent and its dependencies so that they are ready to be used, while also managing these objects' life cycles. The provider may be implemented, for example, as a service locator, an abstract factory, a factory method, or a more complex abstraction such as a framework.

The following is an example. A car (the consumer) depends upon an engine (the dependency) in order to move. The car's engine is made by an automaker (the dependency provider). The car does not know how to install an engine into itself, but it needs an engine in order to move. The automaker installs an engine into the car and the car utilizes the engine to move.

When the concept of dependency injection is used, it decouples high-level modules from low-level services. The result is called the dependency inversion principle.

Javaを使った例

Using the car/engine example mentioned above, the following Java examples show how coupled (manually-injected) dependencies and framework-injected dependencies are typically staged.

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);
}

強く結びついた依存

The following shows a common arrangement with no dependency injection applied:

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.