티스토리 뷰

반응형

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

 

반응형
댓글
  • 프로필사진 Aaron 안녕하세요
    올려주신 소스 참고하여 프로그램 공부 중 질문이 있어 댓글 남겼습니다
    .net5에서는 정상적으로 실행이 잘 되었는데,
    .net framework에서는 "연결된 키가 있어야 됩니다" 라는 문구가 뜨는데 혹시 어떤 부분을 확인해봐야 될까요?
    고맙습니다.^^

    <Application x:Class="myLang.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:myLang"
    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>
    2022.05.24 09:41
  • 프로필사진 Connor Park 본문 하단에 .net framework로 만들어진 소스 첨부했습니다.
    어딘가 namespace 부분이 잘못되어서 그런 메시지가 출력된것으로 보입니다.
    2022.05.24 13:59 신고
  • 프로필사진 Aaron 답변 고맙습니다. 소스 다운 후 테스트 해본결과 원본소스는 런타임 에러가 발생했습니다.
    <d:ResourceDictionary> 이 부분을
    <ResourceDictionary> 이렇게 수정하면 정상적으로 동작 합니다.

    어떤 차이점이 있는지요?
    고맙습니다.
    2022.05.24 17:43
  • 프로필사진 Connor Park d:는 디자인 타임에서만 실행됩니다.
    런타임에서는 실행되지 않습니다.
    <d:ResourceDictionary>는 디자인타임에만 인스턴스되는 리소스 딕셔너리입니다.
    런타임에서는 인스턴스 되지 않습니다.
    사용이유는 xaml 디자인할 때 DR이라는 녀석을 사용해야하는데 디자인타임 리소스가 존재하지 않으면 xaml화면에 오류가 표시되기 때문입니다.
    그럼 그냥 <ResourceDictionary>를 사용하면되는데 굿이 디자인 타임리소스를 이용하는 이유는 <ResourceDictionary>를 사용하면 디자인 타임과 런타임에 모두 DR이 인스턴스가 됩니다. 그런데 DR이 DynamicResource를 사용할 것인지 LocalDynamicResource를 사용할 것인지는 App.xaml.cs에서 결정이되기 때문에 런타임에서 인스턴스 되는 것을 방지하기 위해서 사용했습니다.
    다만, <ResourceDictionary>를 이용해서 DR을 사용하셔도 예제에서는 큰차이가 없지만, 그 차이점을 명확하게 이해하고 사용해야 하기 때문에 일부러 이렇게 사용하게 되었습니다.
    2022.05.26 08:31 신고
  • 프로필사진 Aaron 안녕하세요.
    다음 카페에는 해당 글이 없어 부득이 여기에 질문 드립니다.
    올려주신 소스에서 app.xaml 에서
    <ResourceDictionary>
    이렇게 사용하면 XAML편집 중 resource 키를 변경하면 화면에 즉시 해당 키로 반영됩니다.
    단, 실행 후 "english", "korean" 버튼을 누르면 언어 변경이 안되는데 이 부분은 혹시 설명 부탁드려도 될까요?
    <d:ResourceDictionary>
    이렇게 d:를 붙이면 XAML편집 중 resource키를 변경해도 화면에 갱신은 되지 않지만
    실행하면 정상적으로 화면에 디스플레이되고 언어 변경 버튼을 눌러도 정상적으로 동작 됩니다.
    2022.06.03 16:50
  • 프로필사진 Connor Park 디자인 타임에서 DynamicResource를 사용하기 위해서 d:ResourceDictionary를 사용했고, 런타임에서는
    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();
    }
    여기를 통해서 DR을 활성화합니다.
    그리고, 이 방법의 사용 여부는 직접 판단해서 사용하시면 됩니다. ResourceDictionary에 LocalDynamicResource를 직접 인스턴스 시켜서 사용하셔도 문제는 없습니다.
    다만 그렇게하시면 메서드 부분도 수정해야합니다.
    2022.06.06 12:02 신고
댓글쓰기 폼
반응형
Total
689,181
Today
227
Yesterday
434
«   2022/07   »
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31            
07-07 17:37
글 보관함