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 설정과 구성이 한결 쉬워지므로 적극적으로 활용해 보세요.
추가적 정보는 아래에서 얻을수 있습니다.
'C#' 카테고리의 다른 글
C#/WPF 애플리케이션에서 데이터 기반 테스트(DDT) 구축하기 (0) | 2024.10.30 |
---|---|
C#으로 비동기 프로그래밍 이해하기 (0) | 2024.10.28 |
C#으로 간단한 로깅 시스템 구축하기 (3) | 2024.10.27 |
C#으로 간단한 메모장 애플리케이션 만들기 (0) | 2024.09.17 |
C#으로 파일 시스템 감시 프로그램 만들기 (0) | 2024.09.16 |