생성자 주입과 인터페이스
인터페이스 기반 설계와 생성자 주입을 적용해 구성 요소 간 결합도를 낮추고 단위 테스트를 가능하게 합니다.
생성자 주입과 인터페이스은(는) CoddyKit의 무료 C# Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 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의 가장 큰 이점 중 하나입니다.
// 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
}
}서비스 로케이터 안티패턴 피하기
NOT 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의 주요 장점은 사용하는 코드를 변경하지 않고 한 줄로 구현을 교체할 수 있다는 것입니다. 데이터베이스, 메일 제공업체 또는 기능 플래그를 전환할 때 유용합니다.
// 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를 사용하는 컨트롤러
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 의존도가 높은 클래스의 상용구 코드를 줄여 줍니다
- 서비스 로케이터 안티패턴을 피하고 의존성을 명시적으로 유지하십시오
자주 묻는 질문
“생성자 주입과 인터페이스” 강의는 무료인가요?
네 — “생성자 주입과 인터페이스” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 C# Academy 강의 전체를 잠금 해제할 수 있습니다. C# Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“생성자 주입과 인터페이스”에서 뭘 배우나요?
인터페이스 기반 설계와 생성자 주입을 적용해 구성 요소 간 결합도를 낮추고 단위 테스트를 가능하게 합니다. 브라우저에서 직접 실행하는 실습 코드로 C# Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
C# Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 C# Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“생성자 주입과 인터페이스” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 C# Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 C# Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.