본문 바로가기

C#

C#에서 Dependency Injection (의존성 주입) 이해하기

반응형

1. Dependency Injection이란?

Dependency Injection(DI)은 객체가 의존하는 객체를 직접 생성하지 않고 외부에서 주입받도록 하는 설계 패턴입니다. 이를 통해 클래스 간의 결합도를 낮추고, 코드의 재사용성과 테스트 가능성을 높일 수 있습니다.

DI의 주요 목적은 클래스가 서로 강하게 결합되는 것을 방지하여 유지보수와 확장성이 뛰어난 코드를 작성하는 것입니다. 이 패턴을 적용하면 테스트 환경에서 가짜 객체를 주입하여 쉽게 테스트할 수 있습니다.

2. 기본 예제: DI 없이 직접 객체 생성하기

아래 예제는 간단한 OrderService가 NotificationService에 직접 의존하는 형태입니다.

public class NotificationService
{
    public void SendNotification(string message)
    {
        Console.WriteLine($"알림 전송: {message}");
    }
}

public class OrderService
{
    private NotificationService _notificationService;

    public OrderService()
    {
        _notificationService = new NotificationService();
    }

    public void PlaceOrder()
    {
        Console.WriteLine("주문이 완료되었습니다.");
        _notificationService.SendNotification("주문이 완료되었습니다.");
    }
}

이 코드는 잘 동작하지만, OrderService가 NotificationService에 강하게 의존하고 있어 유지보수 및 테스트가 어렵습니다.

3. 의존성 주입으로 코드 리팩토링

의존성 주입을 적용하여 OrderService가 NotificationService를 직접 생성하지 않도록 리팩토링해 보겠습니다. 이제 OrderService는 NotificationService에 대한 의존성을 생성자가 아닌 외부에서 주입받도록 변경합니다.

public interface INotificationService
{
    void SendNotification(string message);
}

public class NotificationService : INotificationService
{
    public void SendNotification(string message)
    {
        Console.WriteLine($"알림 전송: {message}");
    }
}

public class OrderService
{
    private readonly INotificationService _notificationService;

    // 생성자를 통해 의존성을 주입
    public OrderService(INotificationService notificationService)
    {
        _notificationService = notificationService;
    }

    public void PlaceOrder()
    {
        Console.WriteLine("주문이 완료되었습니다.");
        _notificationService.SendNotification("주문이 완료되었습니다.");
    }
}

이제 OrderService는 INotificationService 인터페이스를 통해 알림 서비스를 사용하므로, 실제 구현을 외부에서 주입받을 수 있습니다.

4. DI 컨테이너 사용하기

C#에서는 .NET Core에 내장된 DI 컨테이너를 사용할 수 있습니다. DI 컨테이너를 사용하면 구성 요소 간의 의존성을 설정하고 애플리케이션이 시작할 때 자동으로 인스턴스를 주입할 수 있습니다.

4.1 .NET의 DI 컨테이너 설정

using Microsoft.Extensions.DependencyInjection;

class Program
{
    static void Main(string[] args)
    {
        // DI 컨테이너 구성
        var serviceProvider = new ServiceCollection()
            .AddTransient<INotificationService, NotificationService>()
            .AddTransient<OrderService>()
            .BuildServiceProvider();

        // 서비스 사용
        var orderService = serviceProvider.GetService<OrderService>();
        orderService.PlaceOrder();
    }
}

위 코드는 DI 컨테이너를 사용하여 OrderService와 NotificationService 간의 의존성을 설정합니다. AddTransient는 요청할 때마다 새로운 인스턴스를 생성하는 라이프사이클을 지정하며, 이 외에도 AddScoped(요청당 하나의 인스턴스)와 AddSingleton(앱 전체에서 하나의 인스턴스만 사용) 등 다양한 라이프사이클 옵션이 있습니다.

5. 의존성 주입을 사용한 테스트 용이성

이제 DI를 사용하면 쉽게 INotificationService의 가짜 객체(Mock)를 주입하여 테스트할 수 있습니다.

5.1 가짜 객체를 사용한 테스트 예제

public class MockNotificationService : INotificationService
{
    public string LastMessage { get; private set; }

    public void SendNotification(string message)
    {
        LastMessage = message;
        Console.WriteLine($"[Mock] 알림 전송: {message}");
    }
}

// 테스트 코드
class Program
{
    static void Main(string[] args)
    {
        // Mock 객체를 주입하여 테스트
        var mockService = new MockNotificationService();
        var orderService = new OrderService(mockService);

        orderService.PlaceOrder();

        Console.WriteLine($"마지막 메시지: {mockService.LastMessage}");
    }
}

위 코드에서는 MockNotificationService를 사용해 실제 알림 전송을 대신하고, OrderService의 동작을 쉽게 검증할 수 있습니다.

6. 의존성 주입의 장점

  • 유연성: 코드의 유연성이 증가하여 다양한 의존성을 손쉽게 교체할 수 있습니다.
  • 테스트 용이성: DI는 테스트 환경에서 가짜 객체를 주입하여 테스트를 쉽게 할 수 있도록 돕습니다.
  • 유지보수성: 클래스 간의 결합도가 낮아져 유지보수하기 쉬운 구조가 됩니다.

7. 마무리

이번 글에서는 C#의 Dependency Injection에 대해 기본 개념과 활용 예제를 다뤄 보았습니다. DI를 통해 애플리케이션의 의존성을 효과적으로 관리하고, 테스트와 유지보수가 쉬운 코드를 작성할 수 있습니다. .NET의 내장 DI 컨테이너를 활용하면 DI 설정과 구성이 한결 쉬워지므로 적극적으로 활용해 보세요.
추가적 정보는 아래에서 얻을수 있습니다.

 

 

종속성 주입 사용 - .NET

이 포괄적인 자습서를 사용하여 .NET 앱에서 종속성 주입을 사용하는 방법을 알아봅니다. C#에서 DI를 이해하려면 이 실용적인 가이드를 따르세요.

learn.microsoft.com

 

 

반응형