コンストラクターインジェクションとインターフェース
インターフェースベースの設計でコンストラクターインジェクションを適用し、コンポーネントを疎結合にして、ユニットテストを可能にします。
「コンストラクターインジェクションとインターフェース」はCoddyKit上の無料C# Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC# Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C# Academyコースには全4レッスンが含まれています。
コンストラクターインジェクションの基礎
コンストラクターインジェクションは、最も一般的なDIパターンです。クラスは依存関係をコンストラクターのパラメーターとして宣言し、コンテナーがインスタンス化の際にそれらを提供します。
これにより、依存関係が明示的で、必須であり、可視化された状態になります。
インターフェイスの定義
まず、契約を定義するインターフェイスから始めます。これにより、依存するコードを変更せずに、異なる実装(実装クラス、モック、インメモリ実装)を差し替えられます。
public interface IProductRepository
{
Task<Product?> GetByIdAsync(int id);
Task<IEnumerable<Product>> GetAllAsync();
Task AddAsync(Product product);
}インターフェイスの実装
具象実装が実際の処理、つまりデータベース呼び出しやHTTPリクエストなどを担当します。利用側は、どの実装を受け取ったかを知る必要がありません。
public class SqlProductRepository : IProductRepository
{
private readonly AppDbContext _db;
public SqlProductRepository(AppDbContext db) => _db = db;
public async Task<Product?> GetByIdAsync(int id)
=> await _db.Products.FindAsync(id);
public async Task<IEnumerable<Product>> GetAllAsync()
=> await _db.Products.ToListAsync();
public async Task AddAsync(Product product)
{
_db.Products.Add(product);
await _db.SaveChangesAsync();
}
}コンストラクターによる利用
サービス層はIProductRepositoryをコンストラクターのパラメーターとして受け取ります。SqlProductRepositoryについて知ることはなく、インターフェイスの契約だけを扱います。
public class ProductService
{
private readonly IProductRepository _repo;
private readonly ILogger<ProductService> _logger;
public ProductService(
IProductRepository repo,
ILogger<ProductService> logger)
{
_repo = repo;
_logger = logger;
}
public async Task<Product?> GetProductAsync(int id)
{
_logger.LogInformation("Fetching product {Id}", id);
return await _repo.GetByIdAsync(id);
}
}依存関係チェーンの登録
チェーン内のすべてのサービスを登録します。コンテナーは依存関係グラフ全体を自動的に解決します。ProductService → IProductRepository → AppDbContextという流れです。
builder.Services.AddDbContext<AppDbContext>(opt =>
opt.UseSqlite("Data Source=app.db"));
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();
builder.Services.AddScoped<ProductService>();モックを使ったテスト
依存関係がインターフェイス経由で注入されるため、ユニットテストではデータベースに接続せずにモックやフェイクを渡せます。これはDIの大きな利点の1つです。
// Using NSubstitute or Moq in a unit test
var mockRepo = Substitute.For<IProductRepository>();
mockRepo.GetByIdAsync(1).Returns(new Product { Id = 1, Name = "Widget" });
var logger = Substitute.For<ILogger<ProductService>>();
var svc = new ProductService(mockRepo, logger);
var result = await svc.GetProductAsync(1);
Assert.Equal("Widget", result?.Name);複数のコンストラクターパラメーター
クラスには多数のコンストラクターパラメーターを指定できます。それぞれが登録されていれば、コンテナーがすべてを解決します。ただし、コンストラクターは役割を絞ってください。パラメーターが多すぎる場合は、クラスが多くの責務を抱えているコードスメルです。
public class OrderService
{
public OrderService(
IOrderRepository orders,
IProductRepository products,
IEmailSender email,
ILogger<OrderService> logger)
{
// all injected by DI container
}
}Service Locatorアンチパターンの回避
IServiceProviderを注入し、ビジネスメソッド内でGetServiceを呼び出してはいけません。これでは依存関係が隠れてしまい、テストが難しくなります。明示的でテストしやすいコードにするため、コンストラクターインジェクションを優先してください。
// BAD
public void Process(IServiceProvider sp)
{
var repo = sp.GetService<IProductRepository>(); // hidden dep!
}
// GOOD
public class Processor
{
private readonly IProductRepository _repo;
public Processor(IProductRepository repo) => _repo = repo;
}プライマリコンストラクター構文(C# 12)
C# 12では、すべてのクラス型でプライマリコンストラクターを使用できるようになりました。クラス宣言にパラメーターを直接記述できるため、DIを多用するコードを簡潔に書けます。
// C# 12 primary constructor
public class ProductService(
IProductRepository repo,
ILogger<ProductService> logger)
{
public async Task<Product?> GetAsync(int id)
{
logger.LogInformation("Getting {Id}", id);
return await repo.GetByIdAsync(id);
}
}実装の切り替え
インターフェイスベースのDIの大きな利点は、利用側のコードを一切変更せず、1行で実装を切り替えられることです。データベース、メールプロバイダー、機能フラグの切り替えなどに役立ちます。
// Switch from SQL to in-memory for integration tests
if (environment.IsEnvironment("Test"))
builder.Services.AddScoped<IProductRepository, InMemoryProductRepository>();
else
builder.Services.AddScoped<IProductRepository, SqlProductRepository>();実践例: DIを使用したController
ASP.NET Coreのコントローラーは、標準でコンストラクターインジェクションを使用します。フレームワークがすべてのパラメーターを解決して渡します。
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly ProductService _svc;
public ProductsController(ProductService svc) => _svc = svc;
[HttpGet("{id}")]
public async Task<IActionResult> Get(int id)
{
var product = await _svc.GetProductAsync(id);
return product is null ? NotFound() : Ok(product);
}
}クイックチェック
DIを使用する際、インターフェイスを前提にプログラミングする主な利点は何ですか。
まとめ: コンストラクターインジェクションとインターフェイス
重要なポイント:
- 依存関係をコンストラクターのパラメーターとして宣言し、コンテナーから提供してもらいます
- サービス契約にはインターフェイスを定義し、具象型ではなくインターフェイスを注入します
- インターフェイスにより、ユニットテストでのモック作成や実装の切り替えが容易になります
- C# 12のプライマリコンストラクターにより、DIを多用するクラスの定型コードを減らせます
- Service Locatorアンチパターンを避け、依存関係を明示的に保ちます
よくある質問
「コンストラクターインジェクションとインターフェース」レッスンは無料ですか?
はい。「コンストラクターインジェクションとインターフェース」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C# Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C# Academyコースには全4レッスンが含まれています。
「コンストラクターインジェクションとインターフェース」で何を学びますか?
インターフェースベースの設計でコンストラクターインジェクションを適用し、コンポーネントを疎結合にして、ユニットテストを可能にします。 ブラウザで直接実行するハンズオンコードでC# Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
C# Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのC# Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。
「コンストラクターインジェクションとインターフェース」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このC# Academyレッスンでコードを書いて実行できますか?
はい。すべてのC# Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- DIコンテナーの基礎
- サービスのライフタイム:Transient、Scoped、Singleton
- コンストラクターインジェクションとインターフェース
- FactoryとOptionsパターン