1. 서론
디자인 패턴 중 전략패턴에 대해 공부하였고 프로젝트에서 수정하면 좋은 부분이 생각나서 반영하였습니다.
- 공부자료 1 : 전략 패턴 예제 작성 + 깃허브 커밋 (참고. JAVA 객체 지향 디자인 패턴 서적)
- 공부자료 2 : 사이트
2. 리팩토링
2 - 1. 리팩토링 부분
현재 LoaTodo의 레이드 체크 방식은 "골드 획득 체크" 방식과 "상위 3개" 두가지 입니다.
- 골드 획득 체크 : 인게임과 동일하게 추가된 레이드 항목에서 골드 획득 지정을 체크해야만 주간 수익에 포함
- 상위 3개 : 추가된 레이드 항목에서 골드 획득이 높은 상위 3개를 자동으로 주간 수익에 포함
그리고 백엔드에서는 boolean 형식으로 캐릭터별 컬럼에 저장하여 로직을 처리하고 있습니다.
[코드 일부]
@ColumnDefault("false")
private boolean goldCheckVersion;
// dtoList 만들기
// goldCheckVersion별 따로
if (character.getSettings().isGoldCheckVersion()) {
makeTodoResponseDtoListGoldCheckVerison(character, todoResponseDtoList);
} else {
// 골드 획득 높은거에서 3개
makeTodoResponseDtoListNotGoldCheckVerison(character, todoResponseDtoList);
maxThree(todoResponseDtoList);
}
이런식으로 코드를 작성했을 때 "새로운 방식(정책)이 추가되면 어떻게 처리할 것인가?"에 대한 문제점이 있습니다.
물론 boolean 형식을 int나 String으로 바꾸고 서비스 로직에서 swith문으로 처리할 수 있지만,
이번에 학습한 전략 패턴을 이용해보려고 합니다.
2 - 2. 리팩토링
정책 인터페이스 생성 [GoldCheckPolicy]
public interface GoldCheckPolicy {
void calcTodoResponseDtoList(Character character, List<TodoResponseDto> todoResponseDtoList);
}
위 인터페이스를 상속받는 클래스 생성
[RaidCheckPolicy]
public class RaidCheckPolicy implements GoldCheckPolicy {
@Override
public void calcTodoResponseDtoList(Character character, List<TodoResponseDto> todoResponseDtoList) {
//기존 로직
}
}
[TopThreePolicy]
public class TopThreePolicy implements GoldCheckPolicy {
@Override
public void calcTodoResponseDtoList(Character character, List<TodoResponseDto> todoResponseDtoList) {
//기존 로직
}
}
여기까진 쉽게 리팩토링 하였지만
그 후 해당 정책을 DB에 저장하고, 저장된 정책에서 로직을 쉽게 꺼내올 수 있게 고민하였고
다음과 같이 Enum 형식으로 엔티티에 저장하였습니다.
[GoldCheckPolicyEnum]
public enum GoldCheckPolicyEnum {
RAID_CHECK_POLICY(new RaidCheckPolicy()),
TOP_THREE_POLICY(new TopThreePolicy());
private final GoldCheckPolicy policy;
GoldCheckPolicyEnum(GoldCheckPolicy policy) {
this.policy = policy;
}
public GoldCheckPolicy getPolicy() {
return policy;
}
}
@Enumerated(EnumType.STRING)
@ColumnDefault("'TOP_THREE_POLICY'")
private GoldCheckPolicyEnum goldCheckPolicyEnum;
public Settings() {
//기존 코드에 추가
this.goldCheckPolicyEnum = GoldCheckPolicyEnum.TOP_THREE_POLICY;
}
클라이언트가 생성할 때 기본값으로 "상위 3개"가 저장됩니다.
또한 출력할 때는 if나 swith를 사용하지않고 저장된 정책에서 바로 로직을 실행할 수 있습니다.
GoldCheckPolicyEnum goldCheckPolicyEnum = character.getSettings().getGoldCheckPolicyEnum();
goldCheckPolicyEnum.getPolicy().calcTodoResponseDtoList(character, todoResponseDtoList);
2 - 3. 테스트
@Test
void toDtoV2Test() {
// given
String username = "test@test.com";
List<Character> characters = memberService.findMember(username).getCharacters();
// when
List<TodoResponseDto> todoResponseDtoList1 = new ArrayList<>();
List<TodoResponseDto> todoResponseDtoList2 = new ArrayList<>();
for (Character character : characters) {
if (!character.getTodoV2List().isEmpty()) {
//기존 로직
if (character.getSettings().isGoldCheckVersion()) {
makeTodoResponseDtoListGoldCheckVerison(character, todoResponseDtoList1);
} else {
makeTodoResponseDtoListNotGoldCheckVerison(character, todoResponseDtoList1);
maxThree(todoResponseDtoList1);
}
//변경 로직
GoldCheckPolicyEnum goldCheckPolicyEnum = character.getSettings().getGoldCheckPolicyEnum();
goldCheckPolicyEnum.getPolicy().calcTodoResponseDtoList(character, todoResponseDtoList2);
}
}
//then
int sum = 0;
for (TodoResponseDto todoResponseDto : todoResponseDtoList1) {
System.out.println(todoResponseDto.getName() + " / " + todoResponseDto.getGold());
sum += todoResponseDto.getGold();
}
System.out.println();
for (TodoResponseDto todoResponseDto : todoResponseDtoList2) {
System.out.println(todoResponseDto.getName() + " / " + todoResponseDto.getGold());
sum -= todoResponseDto.getGold();
}
Assertions.assertThat(sum).isEqualTo(0);
Assertions.assertThat(todoResponseDtoList1).isEqualTo(todoResponseDtoList2);
}
[결과]
임의로 테스트데이터를 정책 두개를 섞어서 테스트를 하였고 성공하였습니다.
3. 후기
전략패턴을 사용하여 성능향상을 하진 않지만, 기존의 로직에서 확장을 할 때 큰 힘을 발휘하며
시스템 유지보수에 도움을 준다.
지금까지 잘 사용하고있는 패턴이라고 하면 '빌더 패턴'하나인데,
이 전략패턴도 좀 더 연습을 한다면 어디에서든지 유용하게 사용할 수 있을꺼같다.