티스토리 뷰

반응형

2022.05.19 - [WPF .NET] - Dependency Inversion Principle(DIP) - 의존성 역전 원칙 part1

이전 포스트에서 의존성에 대해서 알아보았으니 본격적으로 의존성 역전에 대해서 알아 보도록 하겠습니다.

1. Dependency Inversion Principle(DIP)

의존성 역전은 High level과 Low level을 인터페이스를 사용하여 분리하여 개발하는 이야기합니다.

이 포스트에서는 Common Library의 DynamicResource 클래스와 Application의 LocalDynamicResource 클래스가 모두 IDynamicResource에 의존하는 구조로 변경하고, 사용함으로 이를 구현했습니다.

2. IDynamicResource.cs

/// <summary>
/// String Resource 사용 인터페이스
/// </summary>
public interface IDynamicResource
{
    string this[string id] { get; }
    event EventHandler<string> LanguageChanged;
    void ChangeLanguage(string languageCode);
    bool TryGetMember(GetMemberBinder binder, out object result);
}

3. LocalDynamicResource.cs

수정된 DynamicResource.cs는 여기를 참고하시기 바랍니다.

 

DynamicResource를 상속 받은 LocalDynamicResource 클래스를 생성했습니다. 이 클래스는 LocalResource 파일의 자료만 접근해서 가지고 올 수 있도록 되어있으며, id에 해당하는 문자열을 찾지 못하면 Base 클래스인 DynamicResource에게 검색을 요청하고 결과를 반환합니다.

public class LocalDynamicResource : DynamicResource
{
    private readonly ResourceManager _resourceManager;
    public LocalDynamicResource() : base()
    {
        _resourceManager = new ResourceManager(typeof(LocalResource));
    }
    public override string this[string id]
    {
        get
        {
            if (string.IsNullOrEmpty(id)) return null;
            string value = _resourceManager.GetString(id, ClutureInfo);
            if (string.IsNullOrEmpty(value) == false)
            {
                return value;
            }
            else
            {
                return base[id];
            }
        }
    }
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        string id = binder.Name;
        string value = _resourceManager.GetString(id, ClutureInfo);
        if (string.IsNullOrEmpty(value) == false)
        {
            result = value;
            return true;
        }
        else
        {
            return base.TryGetMember(binder, out result);
        }
    }
}

4. XAML 사용법

App.xaml

Application의 StaticResource에 DR이란 이름으로 만들도록 합니다. 그런데, 이전이랑 다른 점은 실행될 때 인스턴스가 되지 않도록 d:라는 네임스페이스를 사용해서 초기화를 합니다.

 

d: 디자인 타임 인스턴스를 만들 때 사용하는 네임스페이스 입니다.

즉, 디자인할 때는 DR을 사용해서 리소스를 바인딩 할 수 있지만, 런타임에서는 만들어지지 않습니다. 그럼, 어떻게 만들어서 사용하는지는 좀더 아래에서 설명하도록 하겠습니다.

<Application
    x:Class="DependencyInversionSample.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:DependencyInversionSample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    StartupUri="MainWindow.xaml"
    mc:Ignorable="d">
    <Application.Resources>
        <d:ResourceDictionary>
            <local:LocalDynamicResource x:Key="DR" />
        </d:ResourceDictionary>
    </Application.Resources>
</Application>

MainWindow.xaml

이전 포스트와 다른 점은 msg_local이라는 id를 가진 TextBlock이 하나 추가된 것입니다.

<StackPanel Grid.Row="1" Margin="5">
    <TextBlock Text="{Binding Source={StaticResource DR}, Path=[wrd_Hello]}" />
    <TextBlock Text="{Binding Source={StaticResource DR}, Path=[msg_DIDescription]}" />
    <TextBlock Text="{Binding Source={StaticResource DR}, Path=[msg_local]}" />
</StackPanel>

5. Code C# 

App.xaml.cs

  • InitializeComponent()보다 먼저 ConfigureServices()가 먼저 호출되어야 합니다.
  • LocalDynamicResource를 인스턴스 시킨 후 StaticResource에 추가하고, 그 녀석을 services에 Singleton으로 추가하는데, 이름을 IDynamicResource로 등록하는 것입니다.
  • 이전 포스트에서는 애플리케이션이 초기화될 때 DynamicResource를 인스턴스 시켜서 StaticResource에 넣어 놓았지만, 이번에는 이 곳에서 자신이 원하는 클래스를 생성해서 사용하게 됩니다. LocalResource를 사용하지 않을 거라면 DynamicResource를 생성해서 사용하고, 그렇지 않다면 LocalDynamicResource를 생성해서 사용할 수 있습니다.
public App()
{
    Services = ConfigureServices();
    this.InitializeComponent();
}

/// <summary>
/// Configures the services for the application. App.xaml.cs
/// </summary>
private static IServiceProvider ConfigureServices()
{
    var services = new ServiceCollection();
    //ViewModel 등록
    services.AddTransient(typeof(MainViewModel));
    var dr = new LocalDynamicResource();
    Current.Resources.Add("DR", dr);
    services.AddSingleton<IDynamicResource>(dr);
    return services.BuildServiceProvider();
}

MainViewModel.cs

 

  • MainViewModel이 인스턴스될 때, IDynamicResource dynamicResource에는 우리가 Singleton으로 등록해 놓았던 LocalDynamicResource의 객체가 주입되고, 그 녀석을 _dr이라는 field에 입력해서 사용을 하면 됩니다.
  • 생성자에서 다른 객체를 받아서 사용하는 것을 Dependency Injection 의존성 주입이라고 합니다.
  • 생성자를 통해서 주입받는 것을 생성자 주입이라고 이야기하며, IoC container의 종류에 따라 여러가지 주입 방법을 사용할 수 있습니다.
public class MainViewModel : ObservableObject
{
    private readonly IDynamicResource _dr;
    public IRelayCommand LanguageChangeCommand { get; set; }
    public MainViewModel(IDynamicResource dynamicResource)
    {
        _dr = dynamicResource;
        Init();
    }
    private void Init()
    {
        LanguageChangeCommand = 
            new RelayCommand<string>
                (OnLanguageChangeCommand);
    }
    private void OnLanguageChangeCommand(string para)
    {
        switch (para)
        {
            case "english":
                 _dr.ChangeLanguage("en-us");
                 break;
            case "korean":
                 _dr.ChangeLanguage("ko-kr");
                 break;
        }
    }
}

6. 실행

Local Test String이 LocalResource file에 있는 문자열 입니다.

7. 결과 확인

이전 포스트에서 이야기를 했던 개선 사항이 개선되었는지 확인하도록 하겠습니다.

 

우리는 LocalDynamicResource라는 이름의 클래스를 추가하고, 내부에서 LocalResource를 바라보도록 만들었기 때문에 Application에서만 사용되는 문자열을 등록하고 사용할 수 있게 되었습니다.

 

MainViewModel에서는 IDynamicResource라는 인터페이스만을 바라도록 수정을 했습니다. 이제 IDynamicResource 인터페이스를 수정하지 않는 이상에는 MainViewModel에서 오류가 발생하는 일은 일어나지 않습니다.

예를 들어서 제가 추가로 RemoteDynamicResource를 만들고, 원격지의 리소스를 가져와서 사용하는 기능을 구현하고 아래와 같이 등록한다면, 한번에 빌드가 성공할 것입니다.

    var dr = new RemoteDynamicResource();
    Current.Resources.Add("DR", dr);
    services.AddSingleton<IDynamicResource>(dr);

이제 다 아시겠죠? 

8. 소스

DependencyInversionSample
DependencyInversionSample.Common 2개의 프로젝트를 참고하시면 됩니다.

WpfTest/DependencyInversionSample at master · kaki104/WpfTest (github.com)

 

GitHub - kaki104/WpfTest

Contribute to kaki104/WpfTest development by creating an account on GitHub.

github.com

아래 리플에서 문의주신 .net framework 셈플 소스입니다. 

확인 부탁드립니다.

WpfApp1.zip
0.04MB

 

반응형
댓글