C++と色々

主にC++やプログラムに関する記事を投稿します。

依存性の注入

依存性の注入(Dependency injection)が実際に役立った場面があったので、メモします。

私はJavacsvファイルを読み込むクラスを書いていました。csvファイルということで、コンストラクタでファイル名を受け取るように実装しました。

package csv;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * CSVファイルを読み込む
 */
public class CSVReader {

  private BufferedReader reader;

  /**
   * コンストラクタ。
   * @param fileName ファイルのパス
   */
  public CSVReader(String fileName) throws IOException {
    reader = new BufferedReader(new FileReader(new File(fileName)));
  }

  /**
   * csvを読み込み、2次元配列として返す
   * @return 2次元配列
   */
  public List<List<String>> read() throws IOException {
    ...
  }
}

この状態のCSVReaderクラスは、Fileに依存しています。ファイル以外から読み込むことが出来ず拡張性が低く、単体テストをする際にテスト用のCSVファイルを作成しなければならず不便でした。
そこで依存性の注入を行うことにより外からどのstreamに依存するかを注入するように設計しなおしました。

package csv;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

/**
 * CSVファイルを読み込む
 */
public class CSVReader {

  private BufferedReader reader;

  /**
   * コンストラクタ。
   * @param stream readerを設定する
   */
  public CSVReader(Reader stream) {
    reader = new BufferedReader(stream);
  }

  /**
   * csvを読み込み、2次元配列として返す
   * @return 2次元配列
   */
  public List<List<String>> read() throws IOException {
    ...
  }
}

おかげで単体テストではテスト用文字列を渡せば良くなり、テストのし易さが上がりました。また、ファイルだけではなく、文字列や標準入力からもカンマ区切り文字列を受けられるようになり汎用的になりました。コンストラクタの検査例外もなくすことが出来ました。以下テストコードです。

package csv;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.Before;
import org.junit.After;

import java.io.StringReader;
import java.util.List;

public class CSVReaderTest {
  private String stubData =
      "0,花子,20,F,163,50\n" +
      "1,太郎,19,M,170,60\n";

  private CSVReader reader;

  @Before
  public void setUp() throws Exception {
    reader = new CSVReader(new StringReader(stubData));
  }

  @After
  public void tearDown() throws Exception {
    reader = null;
  }

  @Test
  public void CSVを読み込めていること() throws Exception {
    List<List<String>> table = reader.read();
    // レコード数は2
    assertThat(table.size(), is(2));

    // 各要素をチェック
    assertThat(table.get(0).get(0), is("0"));
    assertThat(table.get(0).get(4), is("163"));
    assertThat(table.get(1).get(1), is("太郎"));
    assertThat(table.get(1).get(2), is("19"));
  }
}