0Pricing
C# Academy · 강의

생성자 주입과 인터페이스

인터페이스 기반 설계와 생성자 주입을 적용해 구성 요소 간 결합도를 낮추고 단위 테스트를 가능하게 합니다.

생성자 주입과 인터페이스은(는) 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 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. DI 컨테이너 기초
  2. 서비스 수명: Transient, Scoped, Singleton
  3. 생성자 주입과 인터페이스
  4. 팩터리와 옵션 패턴
← C# Academy(으)로 돌아가기