티스토리 뷰

반응형

Dependency Inversion Principle(DIP) 의존성 역전 원칙에 대해 설명을 하는 포스팅입니다. 아래 IoC container, Dependency Injection이 함께 써져있는 것은 의존성 역전 원칙을 구현하기 위해서 필요한 추가적인 개념이기 때문입니다.

 

의존성 역전 원칙을 사용하는 쉬운 예로는 ILogger를 이용해서 로그를 출력하는 애플리케이션을 개발하는 것입니다. 하지만, 이해하기가 쉽지 않을 수 있기 때문에, 제가 사용하는 DynamicResource라는 클래스를 이용해서 구체적으로 설명을 하도록 하겠습니다.

 

더 난해할 수도 있다는 점은 함정!!

1. DynamicResource.cs 클래스 설명

  • Resource File의 String을 XAML이나 Code에서 사용하기 쉽게 만들어 놓은 클래스
  • XAML에 직접 바인딩으로 사용한 문자열은 실행환경에서 실시간으로 변경된 언어에 따라 자동으로 변환됨
  • LanguageChanged 이벤트를 이용해서 Code에서도 언어 변경을 수신해서 처리할 수 있음

2. Resource File 추가 시 주의 사항

General -> Resource File을 선택하신 후 이름을 입력하고 Add를 누르면 추가할 수 있습니다.

Resource Dictionary (WPF)와는 다른 것이니 유의하시기 바랍니다.

3개의 리소스 파일을 추가하셔야 합니다. (이 포스트에서는 영어와 한글만 다루고 있습니다.)

리소스 파일 이름에 언어 코드가 없는 것 1개와 언어 코드가 있는 것 2개

언어 코드가 없는건 Public, 나머지는 No code gen을 선택하시기 바랍니다.

3. ResX Resource Manager

Manage Extension에 ResXManager를 설치하시면 편하게 리소스 파일을 수정할 수 있습니다.

4. Dependency 의존성

의존성 역전 원칙을 다루기 전에 의존성에 대해서 먼저 알아 보도록 하겠습니다.

Common Library 프로젝트에 DynamicResource라는 클래스와 CommonResource라는 리소스 파일이 존재하고, Application 프로젝트 MainViewModel에서 그것들을 사용한다면, 그들에게 의존성이 있다라고 이야기를 하면서 <- 모양으로 표시합니다.

5. DynamicResource.cs

리소스 파일을 이용하는 방법은 ResourceManager를 통해서 사용할 수 있습니다.

 

/// <summary>
/// String Resource 사용 클래스
/// </summary>
public class DynamicResource : DynamicObject
{
    /// <summary>
    /// 언어 변경 이벤트
    /// </summary>
    public event EventHandler<string> LanguageChanged;
    /// <summary> 
    /// 윈도우 리소스로더 
    /// </summary> 
    private readonly ResourceManager _resourceManager;
    protected CultureInfo ClutureInfo = new CultureInfo("en-US");
    /// <summary> 
    /// 생성자 
    /// </summary> 
    public DynamicResource()
    {
        _resourceManager = new ResourceManager(typeof(CommonResource));
    }
    /// <summary> 
    /// 프로퍼티로 호출 
    /// </summary> 
    /// <param name="id"></param> 
    /// <returns></returns> 
    public virtual string this[string id]
    {
        get
        {
            //1. 리소스에서 값 조회
            if (string.IsNullOrEmpty(id)) return null;
            string value = _resourceManager.GetString(id, ClutureInfo);
            if (string.IsNullOrEmpty(value))
            {
                //2. 없으면 키 반환
                value = id;
            }
            return value;
        }
    }
    /// <summary> 
    /// 이름으로 호출 
    /// </summary> 
    /// <param name="binder"></param> 
    /// <param name="result"></param> 
    /// <returns></returns> 
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        string id = binder.Name;
        string value = _resourceManager.GetString(id, ClutureInfo);
        if (string.IsNullOrEmpty(value))
        {
            value = id;
        }
        result = value;
        return true;
    }

    /// <summary> 
    /// 런타임 언어 변경 
    /// </summary> 
    /// <param name="languageCode"></param> 
    public virtual void ChangeLanguage(string languageCode)
    {
        ClutureInfo = new CultureInfo(languageCode);
        Thread.CurrentThread.CurrentCulture = ClutureInfo;
        Thread.CurrentThread.CurrentUICulture = ClutureInfo;
        //윈도우의 언어코드 변경
        foreach (Window window in Application.Current.Windows.Cast<Window>())
        {
            if (!window.AllowsTransparency)
            {
                window.Language = XmlLanguage.GetLanguage(ClutureInfo.Name);
            }
        }
        if (LanguageChanged != null)
        {
            LanguageChanged.Invoke(this, ClutureInfo.Name);
        }
    }
}

6. 사용

App.xaml

DynamicResource 클래스를 애플리케이션이 초기화될 때 StaticResource로 인스턴스를 하고 Key를 DR로 설정합니다.

<Application.Resources>
    <common:DynamicResource x:Key="DR" />
</Application.Resources>

MainWindow.xaml

실제 사용은 아래와 같이 사용합니다. 

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

MainViewModel.cs

뷰모델에서 사용하기위해서는 StaticResource에서 DR이란 이름을 가진 리소스를 가져와서 형변환해서 사용합니다.

    public class MainViewModel : ObservableObject
    {
        private DynamicResource _dr;

        public IRelayCommand LanguageChangeCommand { get; set; }

        public MainViewModel()
        {
            _dr = App.Current.Resources["DR"] as 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;
            }
        }
    }

7. 실행화면

Korean이란 버튼을 클릭하면 아래와 같이 한글 리소스를 사용하도록 변경됩니다.

8. DynamicResource.cs의 개선할 사항?

위의 내용으로 의존성이 어떤것인지에 대해서 알아 볼 수 있었습니다. 의존성이란 어떤것인지 아시겠죠? ^^;;;

 

단순하게 CommonResource 파일에 들어있는 문자열만 사용한다고 하면 지금 상태로 사용해도 문제가 없습니다.

그런데, 애플리케이션에서만 사용할 문자열을 LocalResource 파일에 등록하고, 사용하려면 어떻게 해야 할까요?

또, 그 기능을 구현하기 위해 DynamicResource를 수정하거나 상속 받은 새로운 클래스를 만들어서 사용한다고 하면, MainViewModel.cs에 오류가 발생하지는 않을까요? 오류가 발생해서 수정해야할 때 쉽게 수정이 가능할까요?

다음 포스트에서 이 문제를 해결하는 방법에 대해서 알아 보도록 하겠습니다.

반응형
댓글