NDM

상태 패턴(State Pattern)을 사용하는 방법 본문

기타

상태 패턴(State Pattern)을 사용하는 방법

ndm.jr 2023. 6. 27. 17:29

상태패턴을 알아보고, 어떤 경우에 사용했는지 간략하게 소개하려 한다.

 

실제로 나는 상태패턴을 꽤 사용하고 있으며, Enum, Collection과 함께 사용하는 것을 좋아한다.

 

왜 Enum / Collection과 함께 사용하는지는 이후 예시에서 소개하겠다.

 


## 논의의 시작

 

@Transactional(readOnly = true)
    public List<ExampleDto> getExampleList( // 각종 파라미터, TestSortType sortBy) {

            Comparator<ExampleDto> comparing;
            if(sortBy.equals(TestSortType.DISTANCE)) comparing = Comparator.comparing(ExampleDto::getDistance);
            else if(sortBy.equals(TestSortType.POPULAR)) comparing = Comparator.comparing(ExampleDto::getPopularNum).reversed();
            else throw new IllegalStateException());
            
            // 비즈니스 로직
            // List<ExampleDto> exampleDtos = getExampleDtos();
            
            return exampleDtos.stream().filter()
                            .sort(comparing)
                            .collect(Collectors.toList());
                            .build();
}

 

시작은 단순했다. 레거시 코드에서 Comparator를 구하는 코드가 Service의 조회 메서드에 포함되어 있었던 것

 

하지만 코드 리뷰 도중, 논의가 시작되었다

 

 

 

여기서 메소드를 추출하자는 것은 다들 동의하였고,

추출한 메소드를 Service단에 그대로 둘지, 어딘가로 위임할지가 논제가 되었다

 

결론적으로, 나는 Enum을 활용한 해결 방법이 가장 좋다고 생각한다.

Enum을 활용하기까지의 고민 과정과 예시가 여기에 소개되어 있다.

 

하지만, 상태패턴으로도 해결하는 방법도 공유하고자 포스팅을 작성하였으며,

프로젝트에서 어떻게 활용할 수 있는지 예시를 소개하고자 한다.


## 상태패턴이 뭔데?

 

 

객체 내부의 상태가 바뀜에 따라, 객체의 행동을 바꿀 수 있는 패턴을 말한다.

 

  • 행동의 틀을 정의하는 인터페이스인 State
  • State의 구현체들

 

로 구성되며, Context라는 클라이언트가 State의 메소드를 호출하게 하면 된다.

 

기존의 If-else / Switch 구문은 다음과 같은 문제점이 있었다.

  • 유지보수가 어렵다. 변경 시 메서드 변경 지점이 많다
  • 상태가 추가되는 경우, 유사한 코드를 복붙해야만 한다

 

예를 들면 다음과 같은 경우이다.

 

    public void howtoGo(Transport transport) {

        if(transport == BUS) {
            // 버스를 탈 때 로직
        } else if(transport == SUBWAY) {
            // 지하철 탈 때 로직
        } else if(transport == WALK) {
            // 걸어갈때 로직
        }
    }

 

Transport에 [버스 / 지하철 / 걸어간다] 외에 다른 교통수단이 추가된다면 어떨까?

 

if-else는 계속 추가될 것이고, 코드는 지속적으로 변경될 것이다.

 

이를 방지하기 위해 State Pattern을 사용한다!


## 나는 어떻게 사용했는가?

 

** 모든 예제는 Spring을 사용했다.

 

# case 1) Controller 레벨에서의 파라미터 처리

  • 특정 이벤트가 존재하고, 이벤트 상품과 일반 상품을 조회해야 하는 상황이다.

 

우선, State 인터페이스를 만들어주자.

예제를 만드는 과정에서 한 클래스에 몰아서 코드를 짰기 때문에 static이 붙어있다. static은 무시해도 된다.

 

 

 

이 State를 구현한 구현체들을 만들어주자. 특정 이벤트를 나타내는 구현체와, 일반 구현체 하나를 구현했다.

 

 

Service 클래스를 구현체 클래스에 선언해주면, 이벤트에 맞는 로직을 호출할 수도 있다.

 

Controller단에서는 이렇게 해주면 된다.

 

 

DefaultValue를 선언해 주었으니, 보통의 경우 Normal 구현체를 부를것이고, 그 외에는 특정 이벤트를 부를 것이다.

 

# Collection과 함께 사용한 이유 ??

  • Controller 처음에 List 를 이용해 구현체 클래스들을 빈 주입 받았던 이유는, Stream으로 처리해 If-else를 피하기 위해서이다.
  • Collection을 이용하지 않고 상태 패턴을 사용한다면, 결국 클라이언트에서 If-else나 Switch를 통해 처리해야만 한다
  • Enum과 함께 사용하는 경우는 values() 메소드를 이용하면 Enum 전체 원소를 컬렉션으로 받아와 Stream을 사용할 수 있으니 참고하자

 

# case 2)  SortType 처리하기

 

 

이런 식으로 Interface를 구현하고, 위 예시와 같이 구현체들을 작성해주면 손쉽게 풀어갈 수 있을것이다.

 

다만, 상태패턴은 상태가 늘어날수록 구현체를 추가로 작성해줘야 하니

예시로 든 Event, SortType 처럼 개수가 크게 늘어나지 않는 도메인에 한해서만 사용하는것이 좋겠다


 

## 참조

 

https://kscory.com/dev/design-pattern/state

 

Cory's Developing & Life.

Cory's Developing & Life.

kscory.com