C#

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

TOSUN 2024. 10. 27. 16:53
반응형

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

 

 

반응형