본문 바로가기
.NET/WPF

[WPF] 의존성 주입(Dependency Injection)

by elenakim97 2024. 11. 15.

의존성 주입

객체의 종속성을 객체 내부에서 직접 생성하는 것이 아니라 외부에서 제공하는 방식

public class EmailService
{
    public void SendEmail(string to, string subject, string body)
    {
        // 이메일 전송 로직
    }
}

public class UserService
{
    private EmailService _emailService = new EmailService();

    public void RegisterUser(string email)
    {
        // 사용자 등록 로직
        _emailService.SendEmail(email, "Welcome!", "Welcome to our service!");
    }
}

위 코드에서 `UserService` 클래스는 `EmailService` 클래스에 의존하고 있다.

즉, A 클래스가 B 클래스의 인스턴스를 직접 생성하고 사용한다면 의존성이 강하다고 할 수 있다.

 

위 코드를 의존성 주입 방식으로 변경하면 아래와 같다.

public class UserService
{
    private readonly IEmailService _emailService;

    public UserService(IEmailService emailService)
    {
        _emailService = emailService;
    }

    public void RegisterUser(string email)
    {
        // 사용자 등록 로직
        _emailService.SendEmail(email, "Welcome!", "Welcome to our service!");
    }
}  

`UserService` 클래스가 직접 `EmailService`의 인스턴스를 생성하지 않고, 생성자를 통해 주입 받았다.

 

 

제어의 역전(Inversion of Control)

기존의 모든 제어를 클라이언트 코드가 가지도록 구현하던 것에서, 외부 프레임워크의 흐름 제어를 받도록 하는 것

 

인스턴스의 생성 등은 프레임워크가 담당하며, 우리는 프레임워크에서 요구하는 인터페이스를 구현하면 되기 때문에 더욱 유연하고 모듈화된 소프트웨어를 만들 수 있다.

 

 

IoC 컨테이너

IoC를 구현하는 프레임워크로 객체 관리, 객체 생성, 의존성 관리 등을 수행함

  • `void Register<TInterface, TConcrete>()`: TInterface와 이를 구현하는 TConcrete 클래스 간의 매핑을 등록한다. 이후에 TInterface에 의존하는 인스턴스가 요청될 경우, TConcrete 타입의 개체를 인스턴스에 주입하게 된다.
  • `void Register<TClass>()`: TClass를 등록
  • `T GetInstance<T>()`: 의존성이 해결된 T타입의 인스턴스를 가져온다. 클래스 타입인 경우 해당 클래스의 인스턴스, 인터페이스 타입인 경우 그 인터페이스를 구현하는 클래스의 인스턴스가 반환된다.

 

1) .NET의 IoC 컨테이너

Microsoft.Extensions.DependencyInjection 네임스페이스에 포함

  • `IServiceCollection`: 서비스 등록을 위한 인터페이스
  • `IServiceProvider`: 등록된 서비스의 인스턴스를 제공하는 팩토리
  • `ServiceDescripter`: 서비스 등록 정보를 담는 클래스

 

2) Prism의 IoC 컨테이너

Prism.DryIoc 또는 Prism.Unity Nuget 사용

 

`IContainerRegistry`: 서비스를 컨테이너에 등록하는 인터페이스

  • 관련 메서드: Register, RegisterSingleton, RegisterScoped 등
public partial class App
{
    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        // transient
        containerRegistry.Register<IDateTimeService, DateTimeService>();
        containerRegistry.Register<IMessageService, MessageService>();
        // singleton
        containerRegistry.RegisterSingleton<IViewModelBase, ViewModelBase>();
    }

    protected override Window CreateShell()
    {
        return Container.Resolve<MainWindow>();
    }
}

 

 

`IContainerProvider`: 의존성 관리 기능을 사용하는 클래스에서 컨테이너를 사용할 수 있도록 함

  • 관련 메서드: Resolve, ResolveAll, IsRegistered
public class ViewModelBase : BindableBase, IViewModelBase
{
    /// <summary>
    /// IoC Container
    /// </summary>
    protected IContainerProvider Container { get; }
    /// <summary>
    /// DateTime Service
    /// </summary>
    protected IDateTimeService DateTimeService { get; }
    /// <summary>
    /// Message Service
    /// </summary>
    protected IMessageService MessageService { get; }

    public ViewModelBase()
    {
        
    }

    public ViewModelBase(IContainerProvider containerProvider)
    {
        Container = containerProvider;   
        DateTimeService = Container.Resolve<IDateTimeService>();
        MessageService = Container.Resolve<IMessageService>();
    }
}

 

 

의존성 주입이 필요한 이유

결합도 감소
  • 객체 간 강한 결합을 줄이고 서로 독립적으로 동작할 수 있도록 만든다
  • 이는 코드의 유지보수성을 높이는 데 기여함
유연성 및 확장성
  • 객체의 의존성을 쉽게 교체할 수 있는데, 예를 들어 테스트 환경에서는 mock객체를 사용해 단위 테스트를 쉽게 수행할 수 있다
재사용성
  • 동일한 인터페이스를 구현하는 다양한 클래스를 사용할 수 있다

 


💡 참고

댓글