본문 바로가기

Refactoring

.net Winform 에서 코드 룩업하기 리펙토링 step5

Step5

 스텝4 문제점

 소스상으로 본다면 클래스로 역활을 분리하고 매울 깔끔한 소스의 모습이 유지가 되고 있다. 그리고 코드바인딩은 잘이루어지고 있으며, 각 화면에서 코드를 바인딩 하는 부분이 매우 손쉽게 불러다 쓸 수 있을 정도가 되고 있다. 하지만 ComboBoxCodeBinder 클래스자체는 문제를 해결을 했을지 몰라도 조금만 변화가 필요할 경우 ComboBoxCodeBinder는 엄청난 변경을 수반해야되는 상황에 내 몰리게 된다.

 만약 코드를 바인딩하는 모습이 콤보가 아니라 에디트 박스 두개면 어떻게 할 것인가?

이와 같은 상황이 온다면 에디트 박스 두개에 바인딩 처리를 담당하는 클래스를 만들어야 할 것이다. 이부분은 스펙이 변경되었으니 그렇다고 치더라도. '구분1' 의 값을 참조하는 '구분2'가 콤보일 경우 '구분1'의 값을 참조하기 위해서 ComboBoxCodeBinder는 ComboBoxCodeBinder 를 참조함으로서 그 값을 구하게 되는데 '구분1'은 ComboBoxCodeBinder로 대응이 되지 않는 상황인 것이다.

 스텝4 해결

 UI 컨트롤을 이용하여 코드를 반환하는 것은 어찌보면 우리끼리의 약속이다. 이런 기능을 인터페이스 선언하고 앞으로 앞선 컨트롤의 값을 참조를 인터페이스로 참고하게 된다면 여러가지 CodeBinder 류의 클래스가 만들어진다고 하더라도 각자의 역활은 무리없이 수행할 수 있을 것이다.

추가된 인터페이스, 변경된 클래스 및 화면소스

public interface ICodeBind
{
    string GetCode();
}
public class ComboBoxCodeBinder : ICodeBind
{
    private ComboBox comboBox;
    private ICodeBind refCodeBind;
    private IEnumerable<CodeData> codeDatas;
    public ComboBoxCodeBinder(ComboBox comboBox, IEnumerable<CodeData> codeDatas, ICodeBind refCodeBind)
    {
        this.comboBox    = comboBox;
        this.codeDatas   = codeDatas;
        this.refCodeBind = refCodeBind;

        this.comboBox.Enter += new EventHandler(Event_Enter);
    }
    public ComboBoxCodeBinder(ComboBox comboBox, IEnumerable<CodeData> codeDatas)
    {
        this.comboBox  = comboBox;
        this.codeDatas = codeDatas;

        this.comboBox.Enter += new EventHandler(Event_Enter);
    }
    private void Event_Enter(object sender, EventArgs e)
    {
        ComboBox comboBox = sender as ComboBox;

        var bindingData = this.codeDatas.Select( x => x );
        if (this.refCodeBind != null)
            bindingData = this.codeDatas.Where( x => x.RefCodeDiv.Equals( this.refCodeBind.GetCode() ) ).Select( x => x );

        comboBox.DataSource = null;

        if (bindingData.Count() > 0)
        {
            comboBox.DataSource = new BindingSource()
            {
                DataSource = bindingData
            };
        }

        comboBox.ValueMember   = "Code";
        comboBox.DisplayMember = "Caption";
    }
    public string GetCode()
    {
        object selectedObject = this.comboBox.SelectedItem;

        if (selectedObject == null)
            return string.Empty;

        return ((CodeData)selectedObject).Code;
    }
}
public partial class Form1 : Form
{
    private ICodeBind comboBoxCodeBinder1;
    private ICodeBind comboBoxCodeBinder2;
    private CodeDataRepository codeDataRepository;
    public Form1()
    {
        InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
        this.codeDataRepository  = new CodeDataRepository();
        this.comboBoxCodeBinder1 = new ComboBoxCodeBinder(comboBox1, codeDataRepository.GetCodeDatas("01") );
        this.comboBoxCodeBinder2 = new ComboBoxCodeBinder(comboBox2, codeDataRepository.GetCodeDatas("02"), this.comboBoxCodeBinder1);

        button1.Click += new EventHandler(Event_ClickButton);
    }
    private void Event_ClickButton(object sender, EventArgs e)
    {
        string combo1Value = this.comboBoxCodeBinder1.GetCode();
        string combo2Value = this.comboBoxCodeBinder2.GetCode();

        //something...
    }
}

 코드의 값을 반환하는 것을 ICodeBind라는 인터페이스로 정의하고 UI로부터 코드 값을 읽어내는 클래스는 ICodeBind를 구현하도록 만들면 ComboBox가 아니라. Text, List 등등 상황에 따른 UI 컨르롤이 되더라도 항상 ICodeBind의 구현으로 코드값을 찾아낼 수 있기에 '구분1', '구분2' 이런식으로 코드참조가 케스케이딩 되는 경우에 대응이 가능하다.

 추가적으로 테스트 용의성까지 생긴다. 인터페이스를 구현하는 방식을 이용할 경우 테스트시 실제 데이터가 아니라 가짜객체등으로 테스트를 위한 객체 생성이 수월해지기 때문에 훨씬 더 유연한 코드가 되었다고 볼 수 있다.