Project/LOATODO

[LOATODO] 전략패턴(Strategy Pattern)을 이용한 코드 리팩토링

마볼링 2024. 2. 26. 22:40

1. 서론

디자인 패턴 중 전략패턴에 대해 공부하였고 프로젝트에서 수정하면 좋은 부분이 생각나서 반영하였습니다.

 

- 공부자료 1 : 전략 패턴 예제 작성 + 깃허브 커밋 (참고. JAVA 객체 지향 디자인 패턴 서적)

 

Fetch: 전략패턴 · minhyeok2487/DesignPattern@1a60ca0

minhyeok2487 committed Feb 26, 2024

github.com

 

- 공부자료 2 : 사이트

 

전략 패턴

/ 디자인 패턴들 / 행동 패턴 전략 패턴 다음 이름으로도 불립니다: Strategy 의도 전략 패턴은 알고리즘들의 패밀리를 정의하고, 각 패밀리를 별도의 클래스에 넣은 후 그들의 객체들을 상호교환

refactoring.guru

 

 

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. 후기

전략패턴을 사용하여 성능향상을 하진 않지만, 기존의 로직에서 확장을 할 때 큰 힘을 발휘하며

시스템 유지보수에 도움을 준다.

지금까지 잘 사용하고있는 패턴이라고 하면 '빌더 패턴'하나인데,

이 전략패턴도 좀 더 연습을 한다면 어디에서든지 유용하게 사용할 수 있을꺼같다.