依存性注入を使用して疎結合を実現する

依存性注入を使用して疎結合を実現する

岩佐 孝浩
岩佐 孝浩
6 min read
Architecting

はじめに

この投稿では、Dependency Injection を用いた疎結合なコードの作成方法を解説します。初級開発者を対象に、ソフトウェア設計と保守性を向上させるための実用的なアプローチについて説明します。

疎結合とは

疎結合とは、クラスやモジュールなどのソフトウェアコンポーネントが、インターフェースを介して相互作用するように設計することを指します。一方、密結合なコンポーネントは、変更やテストが困難になります。

密結合の例

以下の例は、TypeScript で書かれた従業員管理機能を示しています。

export class Salary {
  readonly employeeId: number;

  constructor(employeeId: number) {
    this.employeeId = employeeId;
  }

  calculate(): number {
    let salary = 0;
    // ...
    salary = 200000;
    return salary;
  }
}

export class Employee {
  private employeeId: number;
  private name: string;
  private salary: Salary;

  constructor(employeeId: number, name: string) {
    this.employeeId = employeeId;
    this.name = name;
    this.salary = new Salary(this.employeeId);
  }

  // Send an email by Amazon SES. Message text depends on time.
  notify(): void {
    const hour = (new Date()).getHours();
    let title = `Hi ${this.name}`;
    const body = `Current Salary: ${this.salary.calculate()}`;

    if (6 <= hour && hour <= 9) {
      title = `Good morning ${this.name}`;
    } else if (10 <= hour && hour <= 18) {
      title = `How's it going, ${this.name}?`;
    }
    (new SES()).sendEmail({title: title, body: body});
  }
}

密結合の問題

Salary クラスへの密結合

  • this.salary = new Salary(this.employeeId); の行で EmployeeSalary クラスが直接結合しています。
  • Employee#notify メソッドが実際の Salary#calculate メソッドに依存しているため、テスト時に異なる給与計算をシミュレートしたり、エッジケースを処理したりすることが難しくなります。

システムクロックへの密結合

  • const hour = (new Date()).getHours(); の行で Employee クラスがシステムクロックに結合しています。
  • 特定の時刻を対象とした条件ロジックをテストすることが困難になります。

AWS SES への密結合

  • (new SES()).sendEmail(...) の行で Employee が AWS SES サービスに直接結合しています。
  • notify メソッドをテストすると実際のメールが送信され、開発環境では実用的ではありません。

依存性注入による改善

Dependency Injection (DI) を活用することで、コードのモジュール化が進み、テストがより容易になります。

リファクタリング後のコード

export interface ISalary {
  readonly employeeId: number;
  calculate(): number;
}

export interface ISystemDate {
  now(): Date;
}

export interface IMailer {
  send(config: any): void;
}

export class Salary implements ISalary {
  readonly employeeId: number;

  constructor(employeeId: number) {
    this.employeeId = employeeId;
  }

  calculate(): number {
    let salary = 0;
    // ...
    salary = 200000;
    return salary;
  }
}

export class SystemDate implements ISystemDate {
  now(): Date {
    return new Date();
  }
}

export class EmployeeSes implements IMailer {
  send(config: any): void {
    (new SES()).sendEmail(config);
  }
}

export class Employee {
  private employeeId: number;
  private name: string;
  private salary: ISalary;

  constructor(employeeId: number, name: string, salary: ISalary) {
    this.employeeId = employeeId;
    this.name = name;
    this.salary = salary;
  }

  // Send an email by Amazon SES. Message text depends on time.
  notify(systemDate: ISystemDate, mailer: IMailer): void {
    const hour = systemDate.now().getHours();
    let title = `Hi ${this.name}`;
    const body = `Current Salary: ${this.salary.calculate()}`;

    if (6 <= hour && hour <= 9) {
      title = `Good morning ${this.name}`;
    } else if (10 <= hour && hour <= 18) {
      title = `How's it going, ${this.name}?`;
    }
    mailer.send({title: title, body: body});
  }
}

主な改善点

  1. 結合の削減:

    • SalarySystemDateEmployeeSes のようなコンポーネントは、インターフェースを通じて注入されるようになりました。
    • Employee クラスは特定の実装に直接依存しなくなりました。
  2. テストの容易さ:

    • ISalaryISystemDateIMailer のモック実装をテストに使用できます。
    • クロックや SES サービスのようなシステム依存が分離されました。
  3. 依存性逆転の原則:

    • 高水準モジュール(Employee )が低水準モジュール(SalaryDateSES )に依存しなくなります。

まとめ

依存性注入は、疎結合を促進し、コードをより柔軟でテストしやすく、保守性の高いものにします。DI の活用により、コードベースの将来の変更への適応性が向上し、堅牢なテストの実践が可能になります。

Happy Coding! 🚀

岩佐 孝浩

岩佐 孝浩

Software Developer at KAKEHASHI Inc.
AWS を活用したクラウドネイティブ・アプリケーションの要件定義・設計・開発に従事。 株式会社カケハシで、処方箋データ収集の新たな基盤の構築に携わっています。 Japan AWS Top Engineers 2020-2023