DIとは
DI(Dependency Injection)とは、オブジェクトを外部から注入することでコンポーネント間の依存関係を薄くするデザインパターンのこと。
例
DIしない場合
class Manager
{
private readonly Employee employee = new Employee();
public List<Employee> GetStaff()
{
var employeeList = employee.GetEmployees();
//マネージャーが確認したい社員のみにする
//(ここでは仮にリストの1~5番目の社員とする)
var staff = employeeList.GetRange(1, 5);
return staff;
}
}
class Employee
{
public int No;
public List<Employee> GetEmployees()
{
var employeeList = new List<Employee>();
//DBから従業員を10人取得したと仮定する
for (int i = 0; i < 10; i++)
{
employeeList.Add(new Employee() { No = i });
}
return employeeList;
}
}
class Program
{
static void Main(string[] args)
{
Employee employee = new Employee();
var manager = new Manager(employee);
foreach (var staff in manager.GetStaff())
{
Console.Write(staff.No);
//結果 12345
}
}
}
ManagerクラスはEmployeeクラスをクラス内部でnewしているため、ManagerクラスはEmployeeクラスに依存しています。
その結果、以下のような問題が発生します。
-
依存クラスの変更が必要
Employeeクラスを別のクラス(従業員全体に関するメソッドだけ切り出してEmployeeUtilクラスにするなど)で実装することになった場合、Managerクラスにも変更が必要になってしまいます。さらに
Employeeクラスに依存するクラスがManagerクラス以外にもある場合、アプリケーションの各所に変更の影響が発生してしまいます。 -
単体テストができない
テスト用のテーブルが作成前やテスト用のDBが無い場合、GetEmployees()を使った従業員のリストが取得できません。そのため、このメソッド関連の単体テストができなくなってしまいます。
DIする場合
interface IEmployee
{
public List<Employee> GetEmployees();
}
class Manager
{
private readonly IEmployee employee;
public Manager(IEmployee employee)
{
this.employee = employee;
}
public List<Employee> GetStaff()
{
var employeeList = employee.GetEmployees();
//Managerが確認したい社員のみにする
//(ここでは仮にリストの1~5番目の社員とする)
var staff = employeeList.GetRange(1,5);
return staff;
}
}
class Employee : IEmployee
{
public int No;
public List<Employee> GetEmployees()
{
var employeeList = new List<Employee>();
//DBから従業員を10人取得したと仮定する
for (int i = 0; i < 10; i++)
{
employeeList.Add(new Employee() { No = i });
}
return employeeList;
}
}
class Program
{
static void Main(string[] args)
{
IEmployee employee = new Employee();
var manager = new Manager(employee);
foreach (var staff in manager.GetStaff())
{
Console.Write(staff.No);
//結果 12345
}
}
}
IEmployeeインターフェースを作成し、Managerクラスの外部からインターフェースを介してEmployeeクラスを呼び出すようにしました。
その結果、以下のメリットがあります。
-
依存クラスの変更が不要
オブジェクトを外部から設定することで依存クラスの変更が不要になります。例えば、
Employeeクラスを別クラスに置き換えたとしても、Managerクラスへの変更は不要になります。
class EmployeeUtil : IEmployee
{
public List<Employee> GetEmployees() {
//Employeeクラスと同じ処理のため省略
}
}
class Program
{
static void Main(string[] args)
{
//Employee→EmployeeUtilに変更
IEmployee employee = new EmployeeUtil();
var manager = new Manager(employee);
foreach (var staff in manager.GetStaff())
{
Console.Write(staff.No);
//結果 12345
}
}
}
-
単体テストしやすくなる
DBなど外部からデータを取得している場合で単体テストが難しいケースでもテスト用の結果を返すことができるため、単体テストがしやすくなります。例えば、
ManagerクラスのGetStaff()の中で呼ばれるGetEmployees()の結果をMockEmployeeクラスで定義することでテスト用のDBが無い場合でも単体テストができるようになります。
public class MockEmployee : IEmployee
{
public List<Employee> GetEmployees()
{
var employeeMock = new List<Employee>();
for (int i = 0; i < 10; i++)
{
employeeMock.Add(new Employee() { No = i });
}
return employeeMock;
}
}
public class ManagerTest
{
[Test]
public void GetStaffTest()
{
IEmployee employeeMock = new MockEmployee();
var manager = new Manager(employeeMock);
Assert.AreEqual(manager.GetStaff().Count, 5);
}
}
DIコンテナ
上記の例では手動でDIをしていましたが、手動でDIすると実装を変更したいときに修正箇所がアプリケーション各所になってしまいます。(Employeeクラスでnewしていた箇所を全てEmployeeUtilクラスにしたい場合など)
そのような事態を避けるため、DIの依存関係を管理することができるDIコンテナを利用します。DIコンテナを利用することで、オブジェクト間の依存関係を事前に登録しておくことでき、DIする箇所が1箇所になるため保守性を向上させることができます。
C#ではMicrosoft.Extensions.DependencyInjectionと呼ばれるDIコンテナ用のライブラリがあるため、以下ではそちらを利用してDIします。(今回はProgramクラス内ですが、規模が大きいアプリケーションの場合は設定用のファイルを作成した方が依存関係を管理しやすくなります)
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
//事前に依存関係を登録
services.AddSingleton<IEmployee>(new Employee());
var provider = services.BuildServiceProvider();
//サービスの型を取得(ここではEmployeeクラスを取得)
var employee = provider.GetRequiredService<IEmployee>();
var manager = new Manager(employee);
foreach (var staff in manager.GetStaff())
{
Console.Write(staff.No);
//結果 12345
}
}
}
まとめ
DIとはオブジェクトを外部から注入することにより、コンポーネント間を疎結合にすることができるデザインパターン。
疎結合にすることで、アプリケーションを保守しやすく、また単体テストしやすくすることができる。