第36章:newしない設計(DI超入門:手動注入)🎁🔁
この章でやること 🎯✨
- 「
newしない」ってどういう意味?を、テストしやすさで体感する🧪💖 - まずは DI コンテナなしでOK!**手動で注入(手動DI)**できるようになる✋🙂
- 「時計⏰」「乱数🎲」「外部呼び出し🌐」を差し替え可能にするコツを掴む🧩✨
1. なんで「newしない」の?🤔💭(答え:テストがツラいから)
たとえば、こんなコード👇
「今の時刻」「乱数」「外部サービス」を メソッドの中で new してると…
- テストで「今を固定」できない(テストが日付で壊れる)⏰💥
- 乱数が毎回変わる(結果が安定しない)🎲😵
- 外部通信が走る(遅い・失敗する・課金も怖い)🌐💸
2. DIってなに?超ざっくり🍰✨

DI(Dependency Injection)は、 必要な道具(依存)を、外から渡してあげるやり方だよ🎁
- 依存(Dependency)= クラスが使う「道具」🧰 例:時計⏰、乱数🎲、APIクライアント🌐、DBアクセス🗄️
- 注入(Injection)= それを コンストラクタ等で受け取る📦✨
- これでクラスは「自分で道具を作らない」→ 差し替え可能になる🔁💖
.NET の公式ドキュメントでも DI は「依存とクラスを疎結合にするための基本パターン」として説明されてるよ📚✨ (Microsoft Learn)
3. “手動注入”ってなに?🧶🙂(DIコンテナはまだ使わない)
この章は DIコンテナ無しでいくよ!✅ やることはシンプルで、
- クラスの中で
newしない - 必要なものは コンストラクタで受け取る
newするのは 組み立てる場所(入口)だけ(ここ超大事!)🚪✨
この「組み立てる場所」をよく Composition Root(組み立て地点)って呼ぶよ🏗️
4. 例題:クーポン期限チェックを“時刻差し替え可能”にする⏰🎫
4-1. まずはダメな例(テストしづらい)🙅♀️💦
public sealed class Coupon
{
public required DateTimeOffset ExpiresAtUtc { get; init; }
}
public sealed class CouponService
{
public bool CanUse(Coupon coupon)
{
// ❌ クラスの中で「今の時刻」を直取りしてる
return DateTimeOffset.UtcNow <= coupon.ExpiresAtUtc;
}
}
これ、テストで「今」を固定できないから、 日付またぎとかで壊れがち…🥲🕛
4-2. 改善:TimeProvider を受け取る(手動DI)🎁✨
.NET には TimeProvider という「時間の抽象化」があるよ⏳
.NET 8+ でフレームワークに含まれてて、時間依存コードをテストしやすくする目的で用意されてる📚✨ (Microsoft Learn)
public sealed class Coupon
{
public required DateTimeOffset ExpiresAtUtc { get; init; }
}
public sealed class CouponService
{
private readonly TimeProvider _timeProvider;
public CouponService(TimeProvider timeProvider)
{
_timeProvider = timeProvider;
}
public bool CanUse(Coupon coupon)
{
var now = _timeProvider.GetUtcNow();
return now <= coupon.ExpiresAtUtc;
}
}
これで CouponService は「時間」を自分で作らない👏✨ 外から渡された時計で判断するだけ⏰✅
4-3. 組み立て(ここだけ new してOK)🚪🏗️✨
本番の入口(例:Console の Program.cs)で組み立てるよ🙂
var service = new CouponService(TimeProvider.System); // ✅ 本番の時計
var coupon = new Coupon { ExpiresAtUtc = DateTimeOffset.UtcNow.AddHours(1) };
Console.WriteLine(service.CanUse(coupon) ? "OK" : "NG");
TimeProvider.System は「システムの時計」を返してくれるよ⌚✨ (Microsoft Learn)
5. テスト:FakeTimeProvider で「今」を固定する🧪⏰✨
テストでは FakeTimeProvider を使うと便利! 公式にも「時間依存コードのテストのために FakeTimeProvider を使える」と書かれてるよ📚✨ (Microsoft Learn)
using Microsoft.Extensions.Time.Testing;
using Xunit;
public sealed class CouponServiceTests
{
[Fact]
public void 期限切れなら使えない()
{
var fake = new FakeTimeProvider();
fake.SetUtcNow(new DateTimeOffset(2026, 1, 25, 0, 0, 0, TimeSpan.Zero));
var sut = new CouponService(fake);
var coupon = new Coupon
{
ExpiresAtUtc = new DateTimeOffset(2026, 1, 24, 23, 59, 59, TimeSpan.Zero)
};
Assert.False(sut.CanUse(coupon));
}
[Fact]
public void 期限内なら使える()
{
var fake = new FakeTimeProvider();
fake.SetUtcNow(new DateTimeOffset(2026, 1, 25, 0, 0, 0, TimeSpan.Zero));
var sut = new CouponService(fake);
var coupon = new Coupon
{
ExpiresAtUtc = new DateTimeOffset(2026, 1, 25, 0, 0, 1, TimeSpan.Zero)
};
Assert.True(sut.CanUse(coupon));
}
}
SetUtcNow は FakeTimeProvider の API として用意されていて、テストで時刻を進められるよ⏩✨ (Microsoft Learn)
6. “時計・乱数・外部呼び出し”を差し替えるコツ3点 🧠✨
コツ①:依存は「受け取る」🎁
DateTimeOffset.UtcNowを直接使わないRandom.Sharedを直接使わないHttpClientや SDK を直接 new しない
コツ②:境界を薄くする🧅✨
外部APIやDBは「1枚ラップ」して、中心ロジックに入れない🌐🧤 (次の章で Adapter/Wrapper をやるよ!)
コツ③:まず1個だけやる🪄
最初から全部 DI にしないでOK🙂 「壊れやすいところ」から1つずつで十分✨
7. よくある落とし穴⚠️😵
落とし穴A:注入したのに、結局 new してる🥲
「受け取ってるのに使わず、また new する」パターンはありがち!
✅ 対策:new していいのは「入口だけ」🚪✨
落とし穴B:依存が増えすぎてコンストラクタが長い🧳💦
それは「責務が多い」サインかも👃💡
- Extract Class
- 役割ごとにクラスを分ける を疑ってみてね🏠🏠✨
落とし穴C:Service Locator(必要になったら取り出す)🌀
IServiceProvider.GetService() みたいに「必要なときに取り出す」は、
一見便利でも依存が見えなくなってテストが辛くなりがち😵♀️
(手動DIでは特に避けるのがおすすめ!) (Microsoft Learn)
8. ミニ演習📝💖(手を動かすやつ!)
演習1:時計を差し替える⏰
DateTimeOffset.UtcNowを使ってる箇所を1つ探す🔎TimeProviderを受け取る形に直す- FakeTimeProvider でテストを書く🧪✨
演習2:乱数を差し替える🎲
- 抽選ロジックで
Randomを使う - 本番:
new Random() - テスト:
new Random(0)(固定seed)で結果を安定させる✅
演習3:外部通知を差し替える📨
INotifierを作ってNotify(string message)を定義- 本番:Consoleに出す実装
- テスト:呼び出された回数やメッセージを記録する Fake 実装
9. AI拡張の使いどころ🤖✨(“小さく頼む”がコツ)
依存の発見を手伝わせる🔎
- 「このクラスが直接触ってる外部要素(時刻・乱数・I/O)を列挙して」
手動DIへの変形を手伝わせる🧩
- 「この
DateTimeOffset.UtcNowをTimeProvider注入に置き換えて。変更は最小で、差分が小さくなるように」
Fake実装の雛形を作らせる🧱
- 「
INotifierの Fake 実装を作って。受け取ったメッセージを List に貯められるようにして」
※採用前に「差分」と「テストが通るか」を必ず確認ね✅📌
10. 章末チェックリスト✅✨
- クラスの中で
newしてる「道具」を見つけられる🔎 - 依存をコンストラクタで受け取れる🎁
-
newは入口(組み立て地点)に追い出せる🚪 - Fake(偽物)でテストが安定する🧪💖
- 変更が怖くなくなる(差し替えできる)🔁✨