기존코드에서 원래 의도하지 않게 프로그래밍하여 나쁜 냄새를 풍기고 있던 코드를
과감하게 리팩토링했다.
우선, 스프링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 +" 로그인 사용자가 보는 화면입니다.";
}
}
아래 그림과 같이 추상화를 잘해놓고도, 한가지 간과한 점이 있었다!
문제는 바로 로그인/비로그인 조건에 따라 서비스단에서 분기를 태워
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("로그인 사용자가 보는 화면입니다."));
}
}







