2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

依存性の注入(DI)とは

依存性の注入(Dependency Injection/DI) とは、クラスが必要とする他のクラス(依存オブジェクト)を、外部から渡す設計パターンのことです。

簡単に言うと、「クラスの中で必要なものを自分で作るのではなく、外から受け取る」ということです。

依存性って何?

まず「依存性」について理解しましょう。あるクラスAが、別のクラスBを使って動作する場合、「クラスAはクラスBに依存している」と言います。

DIを使わない場合の問題点

具体例を見てみましょう。メール送信機能を持つクラスを考えます。

BadExample.java
public class UserService {
    private EmailService emailService;
    
    public UserService() {
        // クラス内部でEmailServiceを直接生成
        this.emailService = new EmailService();
    }
    
    public void registerUser(String email) {
        // ユーザー登録処理
        System.out.println("ユーザーを登録しました");
        
        // メール送信
        emailService.sendEmail(email, "登録完了");
    }
}

public class EmailService {
    public void sendEmail(String to, String message) {
        System.out.println("メール送信: " + to + " - " + message);
    }
}

このコードには以下のような問題があります:

  1. テストがしにくい: UserServiceをテストする際、実際にメールが送信されてしまう
  2. 変更に弱い: EmailServiceの実装を変えたい場合、UserServiceも修正が必要
  3. 再利用性が低い: 別のメール送信方法を使いたくても、UserServiceを書き換える必要がある

DIを使った場合

では、依存性の注入を使った場合はどうなるでしょうか。

GoodExample.java
// メール送信のインターフェース
public interface IEmailService {
    void sendEmail(String to, String message);
}

// 実際のメール送信実装
public class EmailService implements IEmailService {
    @Override
    public void sendEmail(String to, String message) {
        System.out.println("メール送信: " + to + " - " + message);
    }
}

// テスト用のダミー実装
public class MockEmailService implements IEmailService {
    @Override
    public void sendEmail(String to, String message) {
        System.out.println("(テスト)メール送信: " + to + " - " + message);
    }
}

public class UserService {
    private IEmailService emailService;
    
    // コンストラクタで依存オブジェクトを受け取る(これがDI!)
    public UserService(IEmailService emailService) {
        this.emailService = emailService;
    }
    
    public void registerUser(String email) {
        System.out.println("ユーザーを登録しました");
        emailService.sendEmail(email, "登録完了");
    }
}

使用例

UseCase.java
public class Main {
    public static void main(String[] args) {
        // 本番環境
        IEmailService emailService = new EmailService();
        UserService userService = new UserService(emailService);
        userService.registerUser("user@example.com");
        
        // テスト環境
        IEmailService mockEmailService = new MockEmailService();
        UserService testUserService = new UserService(mockEmailService);
        testUserService.registerUser("test@example.com");
    }
}

DIの3つの注入方法

依存性を注入する方法は主に3つあります。

1. コンストラクタ注入(推奨)

ConstructorInjection.java
public class UserService {
    private final IEmailService emailService;
    
    // コンストラクタで注入
    public UserService(IEmailService emailService) {
        this.emailService = emailService;
    }
}

メリット

  • 必須の依存性を明示できる
  • イミュータブル(不変)なオブジェクトを作れる

2. セッター注入

SetterInjection.java
public class UserService {
    private IEmailService emailService;
    
    // セッターメソッドで注入
    public void setEmailService(IEmailService emailService) {
        this.emailService = emailService;
    }
}

メリット

  • オプショナルな依存性に適している
  • 依存性を後から変更できる

3. インターフェース注入

InterfaceInjection.java
// 注入用のインターフェース
public interface IEmailServiceInjector {
    void injectEmailService(IEmailService emailService);
}

public class UserService implements IEmailServiceInjector {
    private IEmailService emailService;
    
    @Override
    public void injectEmailService(IEmailService emailService) {
        this.emailService = emailService;
    }
}

メリット

  • 注入方法を明示的に定義できる

デメリット

  • 実装が複雑になりがち

Spring BootでのDI

実際の開発では、DIコンテナ(DI Container)と呼ばれるフレームワークを使うことが多いです。Spring Bootを例に見てみましょう。

DIコンテナ: 依存性の注入を自動で行ってくれる仕組みのこと。開発者が手動でnewしてオブジェクトを渡さなくても、フレームワークが自動的に必要なオブジェクトを生成し、注入してくれます。

SpringBootExample.java
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;

@Service
public class EmailService implements IEmailService {
    @Override
    public void sendEmail(String to, String message) {
        System.out.println("メール送信: " + to + " - " + message);
    }
}

@Service
public class UserService {
    private final IEmailService emailService;
    
    // @Autowiredアノテーションで自動的に注入される
    @Autowired
    public UserService(IEmailService emailService) {
        this.emailService = emailService;
    }
    
    public void registerUser(String email) {
        System.out.println("ユーザーを登録しました");
        emailService.sendEmail(email, "登録完了");
    }
}

Spring Bootでは、@Service@Componentなどのアノテーションを付けることで、自動的にDIコンテナがオブジェクトを管理してくれます。

DIのメリット

  1. テストが簡単: モック(テスト用の偽物)を注入できる
  2. 疎結合: クラス間の依存関係が緩やかになり、変更の影響が他のクラスに波及しにくくなる
  3. 再利用性向上: 同じクラスを異なる依存性で使い回せる
  4. 保守性向上: 変更が局所化され、修正が楽になる

まとめ

  • クラス内部で依存オブジェクトを生成せず、外部から受け取る
  • テストしやすく、変更に強いコードになる
  • Spring BootなどのフレームワークがDIを自動化してくれる
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?