[리팩토링]팩토리 메서드(Factory Method)패턴을 활용하여 동적으로 구현객체 생성하기

기존코드에서 원래 의도하지 않게 프로그래밍하여 나쁜 냄새를 풍기고 있던 코드를

과감하게 리팩토링했다.

우선, 스프링MVC에서 기존 서비스단 코드는 아래와 같다.

@Service
public class TestService {
    @Resource(name = "notLoginedWeekDisplay")
    private WeekDisplay notLoginedWeekDisplay;

    @Resource(name = "loginedWeekDisplay")
    private WeekDisplay loginedWeekDisplay;

    public String display(Member member, int currentWeek) {
        if(member.isGuest()) {
            return notLoginedWeekDisplay.display(currentWeek);
        }

        return loginedWeekDisplay.display(currentWeek);
    }
}

display메서드의 의도는 아래와 같다.

  • 로그인사용자와 비로그인 사용자별로 각기 다른 데이터를 가져와 뷰에 보여줌
  • 데이터는 현재주차와 나머지 다른 주차를 비교하여 만들어짐

로그인/비로그인 사용자별로 뷰를 보여주는 부분은 따로 추상화하여

WeekDisplay 추상클래스를 만들었다.

public abstract class WeekDisplay {
    public abstract String compareSmallerOrEqualWeek(int compareWeek);

    //현재주간과 비교주간을 비교해 결과를 만드는 공통로직
    public String display(int currentWeek) {
       //비교주간(ex: 1~4주)이 현재주간과 같거나 이전이면
       //로그인,비로그인 조건별로 다르게 구현
       if (compareWeek <= currentWeek) {
           return compareSmallerOrEqualWeek(compareWeek);
       }

       //비교주간이 현재주간과 다른경우
       ....
    }
}

그리고, WeekDisplay에서 로그인/비로그인 사용자별로 추상메서드

compareSmallerOrEqualWeek()를 각기 다르게 구현하였다.

즉, “템플릿메서드“패턴을 사용하여 공통로직은 추상클래스인 AbstractWeekDisplay에

위치하고 로그인/비로그인 사용자별로 다르게 동작해야 되는 부분은 따로 추상메서드인

compareSmallerOrEqualWeek()로 빼서 각 구현체별로 다르게 구현하였다.

@Component
public class NotLoginedWeekDisplay extends WeekDisplay {
    @Override
    public String compareSmallerOrEqualWeek(int compareWeek) {
        String week = Integer.toString(compareWeek)+"주차";
        reutrn week +" 비로그인 사용자가 보는 화면입니다.";
    }
}

@Component
public class LoginedWeekDisplay extends WeekDisplay {
    @Override
    public String compareSmallerOrEqualWeek(int compareWeek) {
        String week = Integer.toString(compareWeek)+"주차";
        reutrn week +" 로그인 사용자가 보는 화면입니다.";
    }
}

아래 그림과 같이 추상화를 잘해놓고도, 한가지 간과한 점이 있었다!

siege_class_diagram_1

문제는 바로 로그인/비로그인 조건에 따라 서비스단에서 분기를 태워

WeekDisplay의 구현체의 display()메서드를 바로 호출한다는 점이다.

원래 의도대로라면 아래 코드가 되었을 것이다.

    public String display(Member member) {
        //분기로직 없이 동적으로 AbstractWeekDisplay의 구현체를 가져와
        //데이터를 얻어온다.
        //weekDisplay 구현체는 member여부에 따라 달라진다.
        return weekDisplay.display();
    }

그래서, 해당 분기로직을 삭제하고 로그인/비로그인 조건에 따라 동적으로

WeekDisplay의 구현객체를 가져오도록 “팩토리 메서드“패턴을 사용하였다.

    @Service
    public class WeekDisplayFactory {
        private static WeekDisplay notLoginedWeekDisplay;

        @Resource(name = "loginedWeekDisplay")
	private WeekDisplay loginedWeekDisplay;

	public WeekDisplay create(Member member) {
	    if (member.isGuest()) {
	        if (notLoginedWeekDisplay == null) {
		    return new NotLoginedWeekDisplay();
		}

		return notLoginedWeekDisplay;
	    }

	    return loginedWeekDisplay;
	}
    }

위와 같이 팩토리클래스를 사용함으로서, 서비스단은 아래와 같이 간단해졌다.

public String display(Member member) {
    //weekDisplayFactory는 서비스단에서 DI받음
    WeekDisplay weekDisplay = weekDisplayFactory.create(member);

    return weekDisplay.display();
}

드디어, 원래 의도였던 동적으로 WeekDisplay의 구현객체를 가져와서

화면에 보여주는 것을 실현했다.

그리고 서비스단의 분기로직도 사라졌다.

아래는 이번 리팩토링작업동안 진행했던 테스트케이스 코드이다.

@RunWith(MockitoJUnitRunner.class)
public class ServiceTest {
    @Mock
    private WeekDisplayFactory weekDisplayFactory;

    @Captor
    private ArgumentCaptor<Member> memberArgumentCaptor;

    @InjectMocks
    private TestService service;

    private WeekDisplay notLoginedWeekDisplay;
    private WeekDisplay loginededWeekDisplay;

    @Before
    public void setUp() {
        //각종 초기화 코드들..
    }

    @Test
    public void 비로그인사용자의경우() {
        when(weekDisplayFactory.create(guestMember)).thenReturn(notLoginedWeekDisplay);

	String result = service.display(guestMember);

	verify(weekDisplayFactory).create(memberArgumentCaptor.capture());

	assertThat(guestAccount, sameInstance(memberArgumentCaptor.getValue()));
        assertThat(result, is("비로그인 사용자가 보는 화면입니다."));
    }

    @Test
    public void 로그인사용자의경우() {
        when(weekDisplayFactory.create(loginedMember)).thenReturn(loginedWeekDisplay);

	String result = service.display(loginedMember);

	verify(weekDisplayFactory).create(memberArgumentCaptor.capture());

	assertThat(loginedMember, sameInstance(memberArgumentCaptor.getValue()));
        assertThat(result, is("로그인 사용자가 보는 화면입니다."));
    }
}
Advertisements

[리팩토링]팩토리 메서드(Factory Method)패턴을 활용하여 동적으로 구현객체 생성하기”에 대한 3개의 생각

  1. 스프링 프레임워크에서 DI 를 사용할 때의 기본은 주입 받는 클래스의 인터페이스 타입으로 선언을 해야 합니다.. 그래야 프록시가 끼어들 자리도 있고, 느슨한 결합을 할 수 있으며, 객체지향 설계 원칙에 위배되지 않습니다. 객체지향 설계 원칙에 위배되지 않는다는 것은 매우 많은 것을 의미합니다만, 쉽게 말해 유지보수에 용이하고 변화에 민첩하게 대처할 수 있으며, 확장에 열려있고 변경에 닫혀있는 것을 의미합니다.
    위 소스를 보니, 일단 WeekDisplay 를 위한 인터페이스를 만들고,
    WeekDisplay 를 인터페이스를 부분 구현한 골격구현 추상클래스로 만드시고,
    NotLoginWeekDisplay 와 LoginWeekDisplay 를 WeekDisplay 를 상속받는 형태가 더 좋아보입니다.
    위 와 같이 Class형태로 빈을 주입받으려면 아마도 proxy-target-class=”true”를 하셨을텐데 그렇게 되면 aop를 적용하였을 때 cglib 으로 객체를 상속하는 방식으로 부가기능을 적용할텐데, 문제가 많고 권장되지 않습니다. 스프링의 99%가 DI로 이뤄졌다해도 과언이 아니며 DI는 기본은 말씀드렸듯이 인터페이스를 통한 동적객체 주입입니다. 참고하셨으면 좋겠네요 ^^

    • DI를 위해 추상클래스보다 인터페이스를 통해 객체간의 느슨한 결합을 꾀하고, 이는 즉

      객체지향설계원칙 중 “개방폐쇄원칙”을 따름으로서 유지보수에 효율적이다라는 말씀은

      동감합니다.

      다만, WeekDisplay 추상클래스를 구현한 LoginWeekDisplay은 @Component 어노테이션

      을 통해 스프링 빈으로 만들고, NotLoginWeekDisplay은 빈으로 만들 필요가 없어

      일반 스태틱 클래스의 싱글턴 인스턴스로 만들어 사용하였습니다.

      AOP는 @Aspect 어노테이션을 사용하여 부가기능을 넣었고,

      설정은 를 사용하였습니다.

      태클 거셔도 언제든지 환영입니다. 좋은 의견 감사합니다^^

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중