第27章 アンチパターン①:Service Locatorがダメな理由🚫
この章のゴール🎯✨
この章を読み終わったら、次の3つができるようになります😊💡
- 「Service Locator(サービスロケーター)」が DIっぽく見えてDIじゃない と説明できる🌀
- 何が困るか(=設計の“痛み”)を 自分の言葉で言える😖➡️😊
- Service Locatorっぽいコードを コンストラクタ注入に直して、Composition Rootに寄せられる🔧📍
Microsoftの公式ガイドでも「Service Locatorパターンは避けよう」と明言されています。(Microsoft Learn)
1) Service Locatorってなに?🗺️👀

一言でいうと…
「必要な依存を、引数でもらわずに、どこか(コンテナ)に取りに行く」やり方
よくある形はこれ👇
IServiceProviderをクラスが持っていて、必要なときにGetService()/GetRequiredService()で取ってくる🎣- あるいは
staticな “ServiceLocator” から取ってくる🏪(グローバル変数の親戚…!)
これ、見た目は「DIコンテナ使ってるしDIでしょ?」ってなりがちなんだけど… **依存が“見えなくなる”**ので、設計としては別物になっちゃうんだよね🙈💦
2) 何がダメ?困りごと3点セット😵💫💥
① 依存が見えない(読む人が詰む)🙈📦
コンストラクタに書かれてないから、
- このクラス、何が必要なの?
- どんな外部に触ってるの?
が コードを追わないと分からない 😭 レビューも保守もつらい…!
② テストがしにくい(差し替えが面倒)🧪🌀
DIの美味しさは「Fake差し替え」が簡単なことなのに、Service Locatorだと
- テスト側が
IServiceProviderの中身を作らないといけない - どの依存が必要かが隠れてるから、テスト実行して初めて不足に気づく
みたいな “当て勘テスト” になりがち😇💦
③ 実行時に壊れる(気づくのが遅い)💣🏃♀️
GetService() は「登録されてなければ null」になることが多いし、
GetRequiredService() は「無ければ例外」で落ちます💥(Microsoft Learn)
つまり…
- コンパイル時に気づけない
- 起動して、そのコードパスを通って、初めて爆発する
事故るタイミングが遅いのがイヤ〜〜〜!😭
3) ダメな例:Service Locator版(やりがち!)🚫🎣
「注文を確定するサービス」が、必要になるたび依存を取りに行く例👇
using Microsoft.Extensions.DependencyInjection;
public sealed class OrderService
{
private readonly IServiceProvider _provider;
public OrderService(IServiceProvider provider)
{
_provider = provider;
}
public void PlaceOrder()
{
// 必要になったら取りに行く(=依存が隠れる)
var logger = _provider.GetRequiredService<ILogger>();
var payment = _provider.GetRequiredService<IPaymentGateway>();
logger.Log("place order!");
payment.Pay(1000);
}
}
public interface ILogger { void Log(string message); }
public interface IPaymentGateway { void Pay(int amount); }
**どこがイヤ?**😖
OrderServiceのコンストラクタを見るだけだと、何が必要か分からない🙈- テストしたいとき、
IServiceProviderを用意して登録も必要で面倒🧪 - 登録漏れがあってもコンパイルでは分からず、実行時に落ちる💥
Microsoftの公式ガイドでも、この形(GetServiceで取得するService Locator)を避けるよう書かれています。(Microsoft Learn)
4) 正しい形:コンストラクタ注入に直す💉✨
「必要なものは最初に全部もらう」スタイルへ🔧
public sealed class OrderService
{
private readonly ILogger _logger;
private readonly IPaymentGateway _payment;
public OrderService(ILogger logger, IPaymentGateway payment)
{
_logger = logger;
_payment = payment;
}
public void PlaceOrder()
{
_logger.Log("place order!");
_payment.Pay(1000);
}
}
これの良いところ🥰
- 依存がコンストラクタに “見える化” 👀✨
- テストでは
new OrderService(fakeLogger, fakePayment)で終わり🧪💖 - 「登録漏れ」も Composition Root 側で気づきやすい📍
5) “取りに行く”のはどこならOK?📍✅(Composition Rootだけ!)
Service Providerから GetRequiredService するのが完全に禁止…というより、
- アプリの外側(起動時の組み立て場所)だけでやる
- ドメイン/業務ロジックの中ではやらない
が大事だよ〜😊
例:コンソールアプリの “組み立て係” が作って実行する👇
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddSingleton<ILogger, ConsoleLogger>();
services.AddTransient<IPaymentGateway, DummyPaymentGateway>();
services.AddTransient<OrderService>();
using var provider = services.BuildServiceProvider();
// ここ(外側)で解決するのはOK
var app = provider.GetRequiredService<OrderService>();
app.PlaceOrder();
public sealed class ConsoleLogger : ILogger
{
public void Log(string message) => Console.WriteLine(message);
}
public sealed class DummyPaymentGateway : IPaymentGateway
{
public void Pay(int amount) => Console.WriteLine($"pay: {amount}");
}
GetRequiredService 自体は IServiceProvider の拡張として定義されています。(Microsoft Learn)
でも “どこで使うか” が超重要だよ〜📍✨
6) 例外:どうしても “取りに行く” が必要な場面もある🙆♀️🧰
「絶対ダメ!」じゃなくて、フレームワーク境界では例外があるよ〜🙂 代表例が BackgroundService(常駐処理) !
Hosted Service は仕組み上 “スコープが自動で作られない”ので、
スコープサービスを使いたいときは IServiceScopeFactory.CreateScope() でスコープを作るのが公式推奨です。(Microsoft Learn)
ポイントはこれ👇
- アプリの中心ロジックで “取りに行く癖” を広げない🌿
- 境界の層に押し込める(中に漏らさない)🧱✨
7) ミニ演習🔧🧪✨(Service Locator → DIへお直し)
お題🎀
次の「Service Locator版」を、コンストラクタ注入に直してね!
**Before(直す前)**🚫
using Microsoft.Extensions.DependencyInjection;
public sealed class ReportService
{
private readonly IServiceProvider _provider;
public ReportService(IServiceProvider provider)
{
_provider = provider;
}
public string Build()
{
var clock = _provider.GetRequiredService<IClock>();
return $"Report at {clock.Now()}";
}
}
public interface IClock { DateTime Now(); }
ヒント💡
ReportServiceが本当に必要なのはIClockだよね?👀- じゃあ
IClockをコンストラクタでもらえばOK!
After(目標)✅
ReportService(IClock clock)にするBuild()内でGetRequiredServiceを消す
8) 章末まとめ🌸✅(今日のルール)
- ルール①:依存は “引数で見せる” 👀💉
- ルール②:業務ロジックの中で
GetService/GetRequiredServiceしない🚫🎣 (Microsoft Learn) - ルール③:どうしても必要なときは “境界の層” に閉じ込める🧱(例:BackgroundServiceはスコープを作る)(Microsoft Learn)
9) ミニクイズ🎯🧠✨
OrderServiceがIServiceProviderを持ってGetRequiredServiceしてたら、何が「見えなくなる」?🙈- Service Locatorだと、登録漏れに気づくのはいつになりがち?💣
- “取りに行く”のが許されやすい場所はどこ?📍
GetService()とGetRequiredService()の違いは?(挙動の違い)🎣💥 (Microsoft Learn)- BackgroundServiceでスコープサービスを使うとき、公式は何を使うって言ってる?🧺 (Microsoft Learn)
10) AI活用(Copilot / Codex)🤖✨:この章で効く使い方
✅ リファクタ支援プロンプト例
- 「このクラスの依存を列挙して、コンストラクタ注入に直して」
- 「
IServiceProviderを使ってる箇所を探して、Service Locatorになってる所を指摘して」 - 「Composition Root(起動時)に寄せる形で、
GetRequiredServiceを外へ移動して」
✅ レビュー用プロンプト例
- 「この設計、依存が隠れていない?“引数で見えてるか”観点でレビューして」
- 「テストしやすさの観点で、改善点を3つ出して」
必要なら次の章(第28章:循環参照🌀)へ行く前に、いまの演習をもう1〜2問だけ増やして “Service Locator検出力” を鍛える問題も作れるよ💪😊💛