第14章 手を動かす②:手動DIを“きれいに”保つコツ🧹
この章は「DIコンテナを入れずに、手動DI(= new で組み立てる)を読みやすく・壊れにくく保つコツ」を体で覚える回だよ〜😊🌸
(ちなみに今の最新環境だと .NET 10(10.0.2 / 2026-01-13) が “latest” で、C# 14 は .NET 10 対応だよ✨ (Microsoft)
Visual Studio も Visual Studio 2026 が出てて、2026-01-13 に 18.2.0 更新が来てるよ🆕 (Microsoft Learn))
この章のゴール🎯
できるようになったら勝ち🏆✨
- 手動DIの「
Program.csがごちゃごちゃ問題」をスッキリできる🧼 - 依存が増えても「読むのがツライ…😵」にならない形にできる📚
- 「Factory化」「組み立て分割」を使い分けできる🧰
- 依存が多すぎるときに「設計のSOS」を見抜ける📣
まず結論:手動DIを綺麗にする“3つの合言葉”💡✨
newは “組み立て場所” に集める📍- 組み立てを “レシピ化” して分割する🍳
3. 依存が増えすぎたら、クラスの責務を疑う🔍
(公式ガイドラインでも「依存が多いのは SRP 違反のサインかも」って明言してるよ✅ (Microsoft Learn))
ありがちな地獄👹:「手動DIはOK、でも Program が読めない」
例として、学習記録ミニアプリを作るね📒✨ 依存は3つだけにするよ(時計・保存・通知)⏰💾🔔
❌ Before:new がネストして “読めない”
using System.Text.Json;
var app =
new StudyLogApp(
new SystemClock(),
new JsonFileStudyLogStore(
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "studylog.json"),
new JsonSerializerOptions { WriteIndented = true }
),
new ConsoleNotifier()
);
await app.RunAsync();
これ、たった3依存でも「うっ…」ってなるよね😵💫 依存が増えると ネスト地獄+引数地獄が始まる…💥
✅ Step1:Composition Root を “レシピ” にする🍳📍
ポイントは 「まず分解して、名前をつける」 ことだよ😊✨
using System.Text.Json;
var settings = AppSettings.Load();
var app = CompositionRoot.BuildApp(settings);
await app.RunAsync();
static class CompositionRoot
{
public static StudyLogApp BuildApp(AppSettings settings)
{
// 1) 部品を “先に” 変数化(ネストを消す)✨
var clock = new SystemClock();
var jsonOptions = new JsonSerializerOptions
{
WriteIndented = settings.PrettyJson
};
var store = new JsonFileStudyLogStore(settings.DataFilePath, jsonOptions);
var notifier = new ConsoleNotifier();
// 2) 最後に “組み立て” だけを書く(見通し最高)🌈
return new StudyLogApp(clock, store, notifier);
}
}
sealed record AppSettings(string DataFilePath, bool PrettyJson)
{
public static AppSettings Load()
{
var path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"studylog.json"
);
return new AppSettings(path, PrettyJson: true);
}
}
効き目💊✨
- ネストが消えて「何を作ってるか」が見える👀
BuildAppが レシピになって、読みやすい📖
✅ Step2:依存が増えたら “組み立てを分割” する🍰
BuildApp も長くなってきたら、分割してもOK🙆♀️
ただしルールはこれ👇
- 分割しても “組み立て場所” の外に
newを漏らさない📍✨ - 返すのは「完成品」か「小さな部品」だけ🧩
static class CompositionRoot
{
public static StudyLogApp BuildApp(AppSettings settings)
{
var infra = BuildInfrastructure(settings);
return BuildApplication(infra);
}
private static Infra BuildInfrastructure(AppSettings settings)
{
var clock = new SystemClock();
var jsonOptions = new System.Text.Json.JsonSerializerOptions
{
WriteIndented = settings.PrettyJson
};
var store = new JsonFileStudyLogStore(settings.DataFilePath, jsonOptions);
var notifier = new ConsoleNotifier();
return new Infra(clock, store, notifier);
}
private static StudyLogApp BuildApplication(Infra infra)
=> new StudyLogApp(infra.Clock, infra.Store, infra.Notifier);
private sealed record Infra(IClock Clock, IStudyLogStore Store, INotifier Notifier);
}
⚠️ 小ワナ:Infra みたいな “束ねる型” は Composition Root 限定で使おうね🙂
アプリ本体まで渡し始めると「依存が見えにくい」方向に行きがち💦 (束ねるのは 組み立ての都合、ドメインの都合じゃないからね🧠✨)
✅ Step3:Factory化(=作り方だけを別メソッドにする)🏭✨
「設定が絡む」「ちょっと複雑」なやつは Factory化が超効くよ😊
よくある対象🎯
- ファイルパスや接続文字列など 設定が必要🧷
- 生成手順が長い(オプション多い)📦
- 何種類かの実装を選びたい(開発・本番で切替)🔁
static class CompositionRoot
{
public static StudyLogApp BuildApp(AppSettings settings)
{
var clock = new SystemClock();
var store = CreateStore(settings);
var notifier = new ConsoleNotifier();
return new StudyLogApp(clock, store, notifier);
}
private static IStudyLogStore CreateStore(AppSettings settings)
{
var jsonOptions = new System.Text.Json.JsonSerializerOptions
{
WriteIndented = settings.PrettyJson
};
return new JsonFileStudyLogStore(settings.DataFilePath, jsonOptions);
}
}
コツ💡 Factoryは「便利だから全部Factoryにしよ!」じゃなくて、 “複雑なやつだけ” に使うと気持ちいいよ〜😇✨
✅ Step4:引数地獄になったら「Factory化」より先にやること🧠
ここ超大事📣✨ コンストラクタ引数が増えてきたとき、ありがちな誤解👇
- ❌「引数が多い…Factoryで隠そう!」
- ✅「引数が多い…そのクラス、責務多すぎない?」
公式ガイドラインでも「依存が多いのは SRP 違反のサインかも。責務を分けてね」って言ってるよ🔍 (Microsoft Learn)
チェックリスト✅(7個以上なら黄色信号🚥)
- そのクラス、**“やること” を3行で説明できる?**🤔
- “ついで処理” が混ざってない?(ログ整形・保存形式・通知文言など)🧂
- 「手順」をやってるだけなら、**UseCase(アプリ層)**に寄せられない?🎬
- 「細かい詳細」を握ってるなら、**Infra(外側)**に押し出せない?🧱
アンチパターン注意⚠️(手動DIでもやりがち)
1) ❌ “God Factory” 🦖
巨大Factoryが「全部の作り方」を知ってると、結局読めない😵💫 → 分割して “レイヤーごとのレシピ” にしよ🍰
2) ❌ “隠れ Service Locator” 🕳️
IServiceProvider みたいなのをアプリ内部に持ち込むと、依存が見えなくなる🙈
(依存は 引数で見える のが正義👑)
ミニ演習🧪✨(15〜30分)
次の順でやってみてね😊🌸
- いま書いてる小プロジェクト(ConsoleでもOK)を1つ選ぶ📌
newが散ってる場所を探す🔎CompositionRoot.BuildApp()を作ってnewを集める📍- ネストしてる
newを変数に分解する🧩 - 生成が長い部品だけ
CreateXxx()に切り出す🏭 - もし引数が増えすぎたら 責務分割を1回やってみる✂️
Copilot / Codex でのおすすめ使い方🤖✨(そのままコピペOK)
- 「この
Program.csの new ネストを解消して、Composition Root に分割して。挙動は変えないで」🧹 - 「依存関係を 矢印(A → B) で箇条書きにして、組み立て順のレシピを提案して」🗺️
- 「このクラスのコンストラクタ引数が多い。責務分割案を3つ(メリット/デメリット付き)出して」✂️
(VS 2026 は Copilot 統合がより深くなってるよ〜🤖✨ (Microsoft Learn))
章末まとめ🌷
- 手動DIはまだ全然戦える💪✨
- コツは「
newを集めて、レシピ化して、分割する」📍🍳 - 引数が増えすぎたら Factoryで隠す前に SRP を疑う🔍(公式にもそう書いてある✅ (Microsoft Learn))
必要なら、この章の演習用に「あなたの既存コード(短めでOK)」を貼ってくれたら、第14章のルールに沿って“手動DIを綺麗にするリファクタ案”を一緒に作るよ〜😊💉✨