MS_IoCAOPDI - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki

IoC、AOP → DI → 依存性反転原則

概要

フレームワークやクラスライブラリなどの開発に必要になる知識。

補足(本ページの読み方): タイトルの矢印は
**「上位の目的 → それを実現する技術」**という向きを表している。

【何をしたいか(目的・原則)】        【どう実現するか(技術)】

  共通化技法
    ├─ IoC(コードの中で解決)  ──┐
    └─ AOP(コードの外で解決)  ──┤
                                   ├──→  DI(依存性の注入)
  依存性反転原則(差し替え)  ─────┘

つまり、

  • IoC / AOP / 依存性反転原則は「考え方」
  • DI はそれらを実装する「手段」

という関係である。
名前が似ていて混同されやすいが、層が違うという点が本ページの主旨。

詳細

上記の共通化技法IoCAOP)、
依存性反転原則で使用される技術。

共通化技法

各メソッド内に別のメソッドを呼び出す「処理の共通的パターン」がある場合、
オブジェクト指向ではこれを共通化できず、処理が散在してしまう問題に対するソリューション。

補足(「散在してしまう問題」とは): 具体例で言うと、
ロギング・トランザクション・認可・計測などが典型である。

public void 注文する(Order o)
{
    _log.Info("注文する 開始");        // ← ロギング(散在する)
    using var tx = BeginTransaction(); // ← トランザクション(散在する)
    try
    {
        /* ここだけが本来の関心事 */
        tx.Commit();
    }
    catch { tx.Rollback(); throw; }
    _log.Info("注文する 終了");        // ← ロギング(散在する)
}

こうした**「本来の関心事に対して横断的に現れる関心事」**を
**横断的関心事(cross-cutting concern)**と呼ぶ。

オブジェクト指向の継承・委譲では、
「メソッドの前後に処理を挟む」という形の共通化ができない
(継承は「置き換え」であって「挟み込み」ではない)。
ここが IoC / AOP の出発点である。

コードの中で上記の解決を実現する。

コードの外で上記の解決を実現する。

補足(「中」と「外」の対比): 原文の要約は的確なので、
具体化して補っておく。

IoC(コードの中) AOP(コードの外)
やり方 フレームワーク側が呼ぶ構造にする ウィービングで後から織り込む
テンプレート メソッド、コールバック、DI 属性、プロキシ、IL 書き換え
共通処理の場所 基底クラス/フレームワーク側 別モジュール(アスペクト)
呼び出し側の記述 明示的に見える 見えない(=追いにくい)
// IoC 的(テンプレート メソッド):構造はコードに現れている
public sealed class 注文サービス : TransactionalService
{
    protected override void Execute(Order o) { /* 本来の関心事だけ */ }
}

// AOP 的:属性だけ付ける。ログもトランザクションもコードに現れない
[Logging, Transactional]
public void 注文する(Order o) { /* 本来の関心事だけ */ }

AOP の方が記述量は減るが、
**「コードを読んでも何が起きるか分からない」**という代償がある。
AOP の末尾で原文が「不評ですね」と書いているのは、
この点が実務で嫌われたためである。

ライブラリの差し替え技法として使用される。
実現する技術の名称をそのまま使用して、単に、DIと呼ばれることも多い。

補足(用語の混同を整理する): 原文が指摘する通り、
現場ではすべて「DI」と呼ばれてしまうことが多い。
正確には次の 3 つは別物である。

略語 正式名称 何を指すか
IoC Inversion of Control 制御の流れが反転しているという構造の特徴
DIP Dependency Inversion Principle(依存性反転原則) 上位が下位に依存しないという設計原則(SOLID の D)
DI Dependency Injection(依存性の注入) 外から依存オブジェクトを渡すという実装技法

**DIP は「何を目指すか」、DI は「どうやるか」**である。
DI を使わずに DIP を満たすこともできるし
(引数で渡す、ファクトリを使う等)、
DI を使っていても DIP を満たしていない設計もあり得る
(具象クラスを注入している場合など)。

【DIP を満たしていない】        【DIP を満たす】

  業務ロジック                    業務ロジック
       │ 依存                          │ 依存
       ↓                               ↓
  SqlServerRepository            IRepository(抽象)
                                       ↑ 実装
                                 SqlServerRepository

  = 上位が下位の具象に依存      = 上下とも抽象に依存し、
                                    矢印が「反転」している

「反転」という語は、依存の矢印の向きが反転することを指す。

補足(現在の実務では意識せず使っている): これらの概念は、
現在の .NET ではフレームワークに組み込まれているため、
名前を意識せずに使っている場合が多い。

// Program.cs:これが DI コンテナーへの登録(DIP を前提とした設計)
builder.Services.AddScoped<IOrderRepository, SqlOrderRepository>();

// 利用側:コンストラクタで抽象だけを受け取る(constructor 注入)
public sealed class OrderService(IOrderRepository repo) { ... }

詳細は .NET Core における DI /
ASP.NET Core における DI を参照。

参考

Microsoft Learn


Tags: 移行, プログラミング, .NET開発

⚠️ **GitHub.com Fallback** ⚠️