현대 소프트웨어 개발에서 의존성 주입(Dependency Injection, DI)과 서비스 로케이터(Service Locator) 패턴은 객체 간의 의존 관계를 관리하기 위해 널리 사용되는 두 가지 주요 방법론입니다. 이러한 패턴들은 특히 MVVM(모델-뷰-뷰모델) 아키텍처와 함께 사용할 때 장점이 극대화됩니다. 이 글에서는 C# WPF 애플리케이션에서 Dependency Injection과 Service Locator 패턴을 사용하는 방법을 자세히 설명하고, Microsoft.Extensions.DependencyInjection을 활용한 DI 설정 예제를 제공하겠습니다.
1. Dependency Injection이란?
Dependency Injection은 객체가 다른 객체에 대한 의존성을 코드 내에서 직접 생성하는 대신, 외부에서 주입받도록 설계하는 패턴입니다. 이로 인해 코드 간의 결합도가 낮아지고, 테스트 용이성과 유지보수성이 향상됩니다.
예를 들어, A 클래스가 B 클래스에 의존할 때, B 클래스를 A 클래스의 생성자 내에서 직접 생성하는 대신 B 클래스의 인스턴스를 A 클래스의 생성자에 인자로 전달하여 의존성을 주입합니다. 이렇게 함으로써 A 클래스는 B 클래스의 구체적인 implementation에 의존하지 않게 됩니다.
2. Service Locator 패턴
Service Locator 패턴은 애플리케이션에서 사용되는 서비스를 중앙에서 관리하고, 필요할 때마다 서비스를 요청하여 제공받는 방식입니다. 이 패턴은 DI와 함께 사용되기도 하지만, DI에 비해 종속성을 명시적으로 드러내지 않는다는 단점이 있습니다. 이러한 특성 때문에 Service Locator는 테스트가 어렵고, 유지보수의 복잡성을 증가시킬 수 있습니다.
Service Locator의 구조
public interface IService { /*...*/ }
public class ServiceLocator {
private static readonly Dictionary _services = new();
public static void Register(T service) where T : IService {
_services[typeof(T)] = service;
}
public static T GetService() where T : IService {
return (T)_services[typeof(T)];
}
}
3. MVVM 아키텍처와 Dependency Injection
MVVM 아키텍처는 WPF 애플리케이션에서 UI와 비즈니스 로직을 분리하는 데 도움을 줍니다. 이 아키텍처에서 ViewModel이 View와 Model 간의 상호작용을 관리하며, DI는 ViewModel의 의존성을 관리하는 데 큰 역할을 합니다.
MVVM에서의 DI 장점
- 테스트 용이성: 외부 의존성을 쉽게 Mocking할 수 있어 단위 테스트를 실시하기 용이합니다.
- 유연성: 구현체를 쉽게 변경할 수 있어 코드의 유지보수가 용이합니다.
- 명시적 의존성: 생성자에서 의존성을 명시적으로 나타나게 함으로써, 어떤 의존성이 필요한지 명확히 알 수 있습니다.
4. Microsoft.Extensions.DependencyInjection을 통한 DI 설정
Microsoft.Extensions.DependencyInjection은 .NET Core 애플리케이션에서 DI를 구현하기 위한 경량 라이브러리입니다. WPF 내에서도 이 라이브러리를 활용하여 Dependency Injection을 쉽게 설정할 수 있습니다.
4.1. 패키지 설치
Visual Studio의 NuGet 패키지 관리자에서 Microsoft.Extensions.DependencyInjection
패키지를 설치합니다.
PM> Install-Package Microsoft.Extensions.DependencyInjection
4.2. 서비스 등록
서비스를 등록하기 위해, ServiceCollection
을 사용합니다. 이곳에 애플리케이션에서 필요한 모든 서비스를 등록하게 됩니다.
using Microsoft.Extensions.DependencyInjection;
public class App : Application {
private readonly IServiceProvider _serviceProvider;
public App() {
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
_serviceProvider = serviceCollection.BuildServiceProvider();
}
private void ConfigureServices(IServiceCollection services) {
services.AddTransient();
services.AddSingleton();
}
}
4.3. ViewModel 주입
ViewModel에 DI를 적용하기 위해 XAML에서 ViewModel을 설정하는 방법은 다음과 같습니다. 기본적으로 XAML에서는 ViewModel을 인스턴스화하는 것이 불가능하므로, ViewModelLocator를 사용하여 ViewModel을 가져옵니다.
public class ViewModelLocator {
public MainViewModel MainViewModel => App.ServiceProvider.GetService();
}
4.4. XAML에서 ViewModel 바인딩
XAML에서 ViewModel 액세스를 제공하기 위해, ViewModelLocator를 리소스로 추가하고, 데이터 바인딩을 설정합니다.
5. Dependency Injection과 Service Locator의 비교
DI와 Service Locator는 각각 장단점이 있으며, 특정 상황에서 어떤 방법론이 더 적합할지 판단할 필요가 있습니다.
- Dependency Injection:
- 장점: 테스트 용이성, 결합도 감소, 명확한 의존성 관리
- 단점: 초기 설정이 다소 복잡할 수 있음
- Service Locator:
- 장점: 간단한 설정, 코드의 단순성
- 단점: 테스트가 어려워지고, 의존성이 코드에 명시되지 않음
6. 결론
Dependency Injection과 Service Locator 패턴은 WPF MVVM 아키텍처에서 객체 간의 의존성을 관리하는 데 중요한 역할을 합니다. 이 두 패턴 모두 장단점이 있으므로, 애플리케이션의 요구 사항에 따라 적절한 방법을 선택해야 합니다. Microsoft.Extensions.DependencyInjection을 통해 의존성을 효과적으로 관리함으로써, 더 유지 보수 가능하고 테스트 가능한 코드를 작성할 수 있습니다.
MVVM 패턴의 도입은 구성 요소 간의 명확한 분리를 가능하게 하고, DI 패턴을 활용함으로써 각 구성 요소의 결합도를 낮추어 소프트웨어 개발을 보다 효율적이고 생산적으로 만들어 줍니다. 이러한 방식을 통해 고급 개발자로 성장할 수 있는 기회를 제공합니다.