コンテンツにスキップ

英文维基 | 中文维基 | 日文维基 | 草榴社区

「依存性の注入」の版間の差分

出典: フリー百科事典『ウィキペディア(Wikipedia)』
削除された内容 追加された内容
マークアップ言語(HTML)による依存性の注入例を追加
Cewbot (会話 | 投稿記録)
m Bot作業依頼: sourceタグをsyntaxhighlightタグに置換 (Category:非推奨のsourceタグを使用しているページ) - log
25行目: 25行目:


初めに、一連のサンプルで用いる各コンポーネントの[[インタフェース (情報技術)|インタフェース]]を示す。
初めに、一連のサンプルで用いる各コンポーネントの[[インタフェース (情報技術)|インタフェース]]を示す。
<source lang="java">
<syntaxhighlight lang="java">
public interface IOnlineBrokerageService {
public interface IOnlineBrokerageService {
String[] getStockSymbols();
String[] getStockSymbols();
41行目: 41行目:
void executeTrades();
void executeTrades();
}
}
</syntaxhighlight>
</source>


=== DIを用いない状態 ===
=== DIを用いない状態 ===
以下はDIを用いない場合の実装例である。
以下はDIを用いない場合の実装例である。


<source lang="java">
<syntaxhighlight lang="java">
public class VerySimpleStockTraderImpl implements IAutomatedStockTrader {
public class VerySimpleStockTraderImpl implements IAutomatedStockTrader {


63行目: 63行目:
}
}
}
}
</syntaxhighlight>
</source>


<code>VerySimpleStockTraderImpl</code>[[クラス (コンピュータ)|クラス]]では、直接<code>IStockAnalysisService</code>, <code>IOnlineBrokerageService</code>インタフェースを実装したクラスの[[インスタンス]]を作成しており、これらの実装に深く依存してしまっている。
<code>VerySimpleStockTraderImpl</code>[[クラス (コンピュータ)|クラス]]では、直接<code>IStockAnalysisService</code>, <code>IOnlineBrokerageService</code>インタフェースを実装したクラスの[[インスタンス]]を作成しており、これらの実装に深く依存してしまっている。
70行目: 70行目:
上記のコードを、手動でDIを行うように[[リファクタリング (プログラミング)|リファクタリング]]すると下記のようになる。
上記のコードを、手動でDIを行うように[[リファクタリング (プログラミング)|リファクタリング]]すると下記のようになる。


<source lang="java">
<syntaxhighlight lang="java">
public class VerySimpleStockTraderImpl implements IAutomatedStockTrader {
public class VerySimpleStockTraderImpl implements IAutomatedStockTrader {


98行目: 98行目:
}
}
}
}
</syntaxhighlight>
</source>


この例では、<code>MyApplication.main()</code>が依存性の注入を行っており、<code>VerySimpleStockTraderImpl</code>自体は特定の実装に依存しなくなっている。なお、この実装では'''コンストラクタ注入'''の手法が用いられている。
この例では、<code>MyApplication.main()</code>が依存性の注入を行っており、<code>VerySimpleStockTraderImpl</code>自体は特定の実装に依存しなくなっている。なお、この実装では'''コンストラクタ注入'''の手法が用いられている。
105行目: 105行目:
[[#DIコンテナ|DIコンテナ]]を用いることで、依存性の注入をコード上に直接記述せず、自動的に行うことが可能である。こうした手法を用いる場合、依存性は外部の[[Extensible Markup Language|XML]]ファイルや[[メタデータ]]にて定義する。上記のコードを、XMLを用いるDIコンテナを使用するようリファクタリングした例が下記である。
[[#DIコンテナ|DIコンテナ]]を用いることで、依存性の注入をコード上に直接記述せず、自動的に行うことが可能である。こうした手法を用いる場合、依存性は外部の[[Extensible Markup Language|XML]]ファイルや[[メタデータ]]にて定義する。上記のコードを、XMLを用いるDIコンテナを使用するようリファクタリングした例が下記である。


<source lang="xml">
<syntaxhighlight lang="xml">
<contract id="IAutomatedStockTrader">
<contract id="IAutomatedStockTrader">
<implementation>VerySimpleStockTraderImpl</implementation>
<implementation>VerySimpleStockTraderImpl</implementation>
115行目: 115行目:
<implementation>NewYorkStockExchangeBrokerageServiceImpl</implementation>
<implementation>NewYorkStockExchangeBrokerageServiceImpl</implementation>
</contract>
</contract>
</syntaxhighlight>
</source>


<source lang="java">
<syntaxhighlight lang="java">
public class VerySimpleStockTraderImpl implements IAutomatedStockTrader {
public class VerySimpleStockTraderImpl implements IAutomatedStockTrader {
private IStockAnalysisService analysisService;
private IStockAnalysisService analysisService;
140行目: 140行目:
}
}
}
}
</syntaxhighlight>
</source>


この例では、<code>IAutomatedStockTrader</code>のどの実装を使用するかの判断はDIコンテナに委ねられている。インタフェースが要求されたDIコンテナは、設定ファイルに基づきその実装である<code>VerySimpleStockTraderImpl</code>クラスのインスタンスを返す。さらに、<code>VerySimpleStockTraderImpl</code>の<code>IStockAnalysisService</code>と<code>IOnlineBrokerageService</code>の依存性に対して、同様にコンストラクタ注入を行う。
この例では、<code>IAutomatedStockTrader</code>のどの実装を使用するかの判断はDIコンテナに委ねられている。インタフェースが要求されたDIコンテナは、設定ファイルに基づきその実装である<code>VerySimpleStockTraderImpl</code>クラスのインスタンスを返す。さらに、<code>VerySimpleStockTraderImpl</code>の<code>IStockAnalysisService</code>と<code>IOnlineBrokerageService</code>の依存性に対して、同様にコンストラクタ注入を行う。
149行目: 149行目:
DIを用いることで、単体テストにおいて簡単に依存性をテスト用のクラス([[モックオブジェクト]]等)に差し替えることができる。以下はDIを用いた、前述の<code>VerySimpleStockTraderImpl</code>クラスのテストケースの例である。この例では、<code>IOnlineBrokerageService</code>, <code>IStockAnalysisService</code>インタフェースを実装したテスト用クラスを作成し、DIによりそれを注入することで、実際のクラスを用いることなく、単体テストを実現している。
DIを用いることで、単体テストにおいて簡単に依存性をテスト用のクラス([[モックオブジェクト]]等)に差し替えることができる。以下はDIを用いた、前述の<code>VerySimpleStockTraderImpl</code>クラスのテストケースの例である。この例では、<code>IOnlineBrokerageService</code>, <code>IStockAnalysisService</code>インタフェースを実装したテスト用クラスを作成し、DIによりそれを注入することで、実際のクラスを用いることなく、単体テストを実現している。


<source lang="java">
<syntaxhighlight lang="java">
public class VerySimpleStockBrokerTest {
public class VerySimpleStockBrokerTest {
// IOnlineBrokerageServiceを実装した単純なスタブ
// IOnlineBrokerageServiceを実装した単純なスタブ
193行目: 193行目:
}
}
}
}
</syntaxhighlight>
</source>


実装が[[データベース|DB]]や[[コンピュータネットワーク|ネットワーク]]にアクセスする場合、また古い[[Enterprise JavaBeans|EJB]]のような重たいコンポーネントの場合、そのままでは単体テストを行うことは難しい。しかし、上記のようにDIを用いて依存関係のみをテスト用のものに差し替えることで、本来のテスト対象のプログラムには手を加えることなく、簡単に単体テストを行うことができる。<ref name="itpro20050216" />
実装が[[データベース|DB]]や[[コンピュータネットワーク|ネットワーク]]にアクセスする場合、また古い[[Enterprise JavaBeans|EJB]]のような重たいコンポーネントの場合、そのままでは単体テストを行うことは難しい。しかし、上記のようにDIを用いて依存関係のみをテスト用のものに差し替えることで、本来のテスト対象のプログラムには手を加えることなく、簡単に単体テストを行うことができる。<ref name="itpro20050216" />

2020年7月5日 (日) 22:49時点における版

依存性の注入(いそんせいのちゅうにゅう、: Dependency injection)とは、コンポーネント間の依存関係をプログラムソースコードから排除するために、外部の設定ファイルなどでオブジェクトを注入できるようにするソフトウェアパターンである。英語の頭文字からDIと略される。

概要

DIを利用したプログラムを作成する場合、コンポーネント間の関係はインタフェースを用いて記述し、具体的なコンポーネントを指定しない。具体的にどのコンポーネントを利用するかは別のコンポーネントや外部ファイル等を利用することで、コンポーネント間の依存関係を薄くすることができる。

依存関係がプログラムから外部に取り除かれることで、以下のようなメリットが発生する。[1]

Dependency injectionという用語を作成したのはソフトウェア開発者のマーティン・ファウラーである。類似の概念としてそれ以前から制御の反転 (IoC) と呼ばれるアイデアが存在していたが、それを整理・範囲を限定することでDIが生み出された。現在では代表的なDIコンテナとして知られるSpring Frameworkも、誕生当初はDIではなくIoCという表現を用いていた。DIは2000年代前半のJavaによる開発において、極めて複雑な標準仕様となっていたJ2EEの特にEJBに対する批判を背景に広く用いられるようになった。[1] その概念は後に標準仕様にも取り込まれ、2007年Java EE 5では限定的な機能を備えたEJB 3.0が、2009年のJava EE 6ではより汎用的なDIコンテナとしての機能を備えたCDIが定義されている。[2]

DIの種類

プログラムに依存性を注入する方法としては、以下のような手法が存在する。

インタフェース注入
注入用のインタフェースを定義して注入を行う方法
setter 注入
setter メソッドを定義して注入を行う方法
コンストラクタ注入
コンストラクタを定義して注入を行う方法

DIの例として、以下にJavaによるDIを用いない場合と手動でのDI、ならびにDIコンテナをイメージした自動でのDIのサンプルコードを示す。

初めに、一連のサンプルで用いる各コンポーネントのインタフェースを示す。

public interface IOnlineBrokerageService {
    String[] getStockSymbols();
    double getBidPrice(String stockSymbol);
    double getAskPrice(String stockSymbol);
    void putBuyOrder(String stockSymbol, int shares, double buyPrice);
    void putSellOrder(String stockSymbol, int shares, double sellPrice);
}

public interface IStockAnalysisService {
    double getEstimatedValue(String stockSymbol);
}

public interface IAutomatedStockTrader {
    void executeTrades();
}

DIを用いない状態

以下はDIを用いない場合の実装例である。

public class VerySimpleStockTraderImpl implements IAutomatedStockTrader {

    private IStockAnalysisService analysisService = new StockAnalysisServiceImpl();
    private IOnlineBrokerageService brokerageService = new NewYorkStockExchangeBrokerageServiceImpl();

    public void executeTrades() {
        .
    }
}

public class MyApplication {
    public static void main(String[] args) {
        IAutomatedStockTrader stockTrader = new VerySimpleStockTraderImpl();
        stockTrader.executeTrades();
    }
}

VerySimpleStockTraderImplクラスでは、直接IStockAnalysisService, IOnlineBrokerageServiceインタフェースを実装したクラスのインスタンスを作成しており、これらの実装に深く依存してしまっている。

手動でのDI

上記のコードを、手動でDIを行うようにリファクタリングすると下記のようになる。

public class VerySimpleStockTraderImpl implements IAutomatedStockTrader {

    private IStockAnalysisService analysisService;
    private IOnlineBrokerageService brokerageService;

    public VerySimpleStockTraderImpl(
            IStockAnalysisService analysisService,
            IOnlineBrokerageService brokerageService) {
        this.analysisService = analysisService;
        this.brokerageService = brokerageService;
    }
    public void executeTrades() {
        
    }
}

public class MyApplication {
    public static void main(String[] args) {
        IStockAnalysisService analysisService = new StockAnalysisServiceImpl();
        IOnlineBrokerageService brokerageService = new NewYorkStockExchangeBrokerageServiceImpl();

        IAutomatedStockTrader stockTrader = new VerySimpleStockTraderImpl(
            analysisService,
            brokerageService);
        stockTrader.executeTrades();
    }
}

この例では、MyApplication.main()が依存性の注入を行っており、VerySimpleStockTraderImpl自体は特定の実装に依存しなくなっている。なお、この実装ではコンストラクタ注入の手法が用いられている。

自動的なDI

DIコンテナを用いることで、依存性の注入をコード上に直接記述せず、自動的に行うことが可能である。こうした手法を用いる場合、依存性は外部のXMLファイルやメタデータにて定義する。上記のコードを、XMLを用いるDIコンテナを使用するようリファクタリングした例が下記である。

    <contract id="IAutomatedStockTrader">
        <implementation>VerySimpleStockTraderImpl</implementation>
    </contract>
    <contract id="IStockAnalysisService" singleton="true">
        <implementation>StockAnalysisServiceImpl</implementation>
    </contract>
    <contract id="IOnlineBrokerageService" singleton="true">
        <implementation>NewYorkStockExchangeBrokerageServiceImpl</implementation>
    </contract>
public class VerySimpleStockTraderImpl implements IAutomatedStockTrader {
    private IStockAnalysisService analysisService;
    private IOnlineBrokerageService brokerageService;

    public VerySimpleStockTraderImpl(
            IStockAnalysisService analysisService,
            IOnlineBrokerageService brokerageService) {
        this.analysisService = analysisService;
        this.brokerageService = brokerageService;
    }
    public void executeTrades() {
        
    }
}

public class MyApplication {
    public static void main(String[] args) {
        IAutomatedStockTrader stockTrader =
            (IAutomatedStockTrader) DependencyManager.create(IAutomatedStockTrader.class);
        stockTrader.executeTrades();
    }
}

この例では、IAutomatedStockTraderのどの実装を使用するかの判断はDIコンテナに委ねられている。インタフェースが要求されたDIコンテナは、設定ファイルに基づきその実装であるVerySimpleStockTraderImplクラスのインスタンスを返す。さらに、VerySimpleStockTraderImplIStockAnalysisServiceIOnlineBrokerageServiceの依存性に対して、同様にコンストラクタ注入を行う。

DIコンテナには数多くの種類があり、上で示した例はそのごく一部でしかない。実際にはDIコンテナごとに様々な手法が用いられている。

DIを用いた単体テスト

DIを用いることで、単体テストにおいて簡単に依存性をテスト用のクラス(モックオブジェクト等)に差し替えることができる。以下はDIを用いた、前述のVerySimpleStockTraderImplクラスのテストケースの例である。この例では、IOnlineBrokerageService, IStockAnalysisServiceインタフェースを実装したテスト用クラスを作成し、DIによりそれを注入することで、実際のクラスを用いることなく、単体テストを実現している。

public class VerySimpleStockBrokerTest {
    // IOnlineBrokerageServiceを実装した単純なスタブ
    public class StubBrokerageService implements IOnlineBrokerageService {
        public String[] getStockSymbols() { 
            return new String[] {"ACME"};
        }
        public double getBidPrice(String stockSymbol) {
            return 100.0; // (テストに十分な値)
        }
        public double getAskPrice(String stockSymbol) { 
            return 100.25;
        }
        public void putBuyOrder(String stockSymbol, int shares, double buyPrice) {
             Assert.Fail("Should not buy ACME stock!");
        }
        public void putSellOrder(String stockSymbol, int shares, double sellPrice) {
             // このテストでは使用しない
             throw new NotImplementedException(); 
        }
    }

    public class StubAnalysisService implements IStockAnalysisService {
        public double getEstimatedValue(String stockSymbol) {
            if (stockSymbol.equals("ACME")) 
                return 1.0;
            return 100.0;
        }
    }

    public void TestVerySimpleStockTraderImpl() {
        // このテスト専用の依存性を指定するため、DIコンテナに直接登録している
        DependencyManager.register(
            IOnlineBrokerageService.class,
            StubBrokerageService.class);
        DependencyManager.register(
            IStockAnalysisService.class,
            StubAnalysisService.class);

        IAutomatedStockTrader stockTrader =
            (IAutomatedStockTrader) DependencyManager.create(IAutomatedStockTrader.class);
        stockTrader.executeTrades();
    }
}

実装がDBネットワークにアクセスする場合、また古いEJBのような重たいコンポーネントの場合、そのままでは単体テストを行うことは難しい。しかし、上記のようにDIを用いて依存関係のみをテスト用のものに差し替えることで、本来のテスト対象のプログラムには手を加えることなく、簡単に単体テストを行うことができる。[1]

HTML

マークアップ言語であるHTML (HyperText Markup Language) でも依存性の注入がおこなわれる。

WebComponents(カスタム要素 + Template要素+ ShadowDOM)の登場により、巨大なHTMLファイルを小さなHTML要素コンポーネントの集合として記述することが可能になった。しかし大きなコンポーネント Big が小さなコンポーネント Small を包む形でコーディングすると、Big が Small に依存してしまう。そこでslot要素を用いた依存性の注入がおこなわれる。slot要素は弱いinterfaceとして働き、slot要素を用いて定義されたカスタム要素を利用する際に依存性をタグで囲むことで注入できる。

下記の例では大きなコンポーネント<my-element-with-slot>が2つの受け入れ可能slotを持っている。利用時にslotを指定したspan要素を挿入することで、<my-element-with-slot>はspan要素に直接依存せずにspan要素を利用できる。プログラミング言語のような明示的interfaceがない(interfaceによる型指定slot要素がない)ために型支援を受けた安全な依存性の注入は現時点ではおこなえないが、適切に設計することで依存性を切り分けることは可能である。

    <!--when define-->
    <script>
      class myElementWithSlot extends HTMLElement {
        constructor() {
          super();
          const shadowRoot = this.attachShadow({ mode: "open" });
          shadowRoot.innerHTML = `
            <h2>My Element</h2>
            <h3>inserted #1: <slot name="slot1">no contents</slot></h3>
            <h4>inserted #2: <slot name="slot2">no contents</slot></h4>
          `;
        }
      }
      customElements.define("my-element-with-slot", myElementWithSlot);
    </script>

    <!--when use-->
    <my-element-with-slot>
      <span slot="slot1">dependency-one</span>
      <span slot="slot2">dependency-two</span>
    </my-element-with-slot>

DIコンテナ

DIの機能を提供するフレームワークはDIコンテナと呼ばれる[1]。 主なDIコンテナとしては、下記のようなものが存在する。

Java
.NET
PHP

脚注

  1. ^ a b c d Java開発を変える最新の設計思想「Dependency Injection(DI)」とは”. ITPro (2005年2月18日). 2014年2月20日閲覧。
  2. ^ Java EE 6: Understanding Contexts and Dependency Injection (CDI), Part 1”. オラクル (2010年5月25日). 2014年2月20日閲覧。

関連項目