본문 바로가기
JAVA/Spring & Java 학습 기록

밸리데이션 정책에 전략 패턴을 적용해보았다

by 구본식 2024. 2. 9.

해당 포스터는 Volunteer 프로젝트의 관한 내용입니다.

 

객체지향 프로그래밍에서 추상화는 많은 이점을 제공한다.

인터페이스, DI, 추상 클래스 등은 런타임에 구체 클래스가 정해지므로 유연한 확장과 의존성이 줄어든다는 점이 존재한다.

이처럼 구현을 하면서 if문 분기 처리가 많이 나오거나, 추상화가 가능한 부분이 나온다면 추상화를 이용한 패턴을 고려해 본다.

프로젝트를 리팩터링 하면서 유효성 검사에 분기처리가 필요했고, 추상화와 밀접하게 연관된 전략 패턴을 적용해 보았다.

 

전략 패턴의 개념은 다음과 같다.

실행(런타임) 중에 다형성을 기반으로 알고리즘 전략을 선택하여, 동적으로 전략을 수정할 수 있는 행위 디자인 패턴.
어떤 행동을 수행하는 알고리즘이 여러 개 있을 때, 동작을 미리 전략으로 정의함으로써 손쉽게 전략을 교체할 수 있는 패턴.

 

여러 전략 알고리즘이 존재하고, 런타임에 동적으로 전략이 교체되어야 하는 상황에 적합한 디자인 패턴이다.

인터페이스-구현체를 통해 새로운 전략을 쉽게 확장할 수 있다.

또, 특정 컨텍스트는 코드 변경 없이 새로운 전략을 추가할 수 있어 개방-폐쇄 원칙(OCP)을 따르게 된다.

(더 자세한 내용은 해당 링크-노션 참고)

 

개인적인 생각으로 리팩토링 한 코드이므로, 해당 방식보다 더 좋은 방식이 반드시 존재할 것이다. 

따라서, 초기 상황의 문제와 어떻게 개선되었는지를 중점적으로 살펴볼 것이다.


상황

봉사를 모집하는 글에는 정기/비정기 봉사 모집이 존재한다.

특히, 정기 봉사의 경우 매달 몇째 주 반복, 매주 반복 등 특정 반복 주기가 존재하고, 주기 데이터들은 각 주기에 맞게 검증이 필요하다.

그럼 코드로 더 자세히 살펴보자.

RecruitmentService
DTO
RepeatPeriod(Domain)

각 반복 주기를 분기 처리하여 RepeatPeriod 도메인 객체를 생성한다.

또, 각 주기에 맞는 데이터 검증을 위해 생성자가 분리되어 있다.

참고로, 위 코드에서 DTO에서 분기 처리를 없애고, 생성 메서드 내에서 분기 처리를 해도 상관없다.

 

이 부분의 핵심은  반복 주기(Period)가 추가될수록, 코드의 변경이 끊임없이 요구되는 것이다.

실제로 회의를 하면서 "매일 진행하는 봉사도 있는 거 아니야?"라는 의견이 나올 정도로 요구사항이 언제 추가되어도 이상하지 않다.

만약, 해당 기능이 추가될 경우 분기 처리 후, if(period != Period.EVERY || week != Week.NONE || dayOfWeek != Day.NONE) 조건을 검증해야 한다.

이렇게 하나둘씩 추가되면 분기 처리가 점차 많아지고, if문의 지옥에 빠질 것이다.

또, 분류가 추가될 때마다 기존 클래스에 변경이 계속 발생하므로 OCP 원칙에 어긋난다.

(물론 해당 코드는 간단하므로 문제가 크게 되지는 않는다.)


개선

문제 코드에서 반복 주기 타입에 따라 데이터 검증이 달라지기 때문에 생성자를 분리했다.

또, 각 반복 주기 타입에 맞는 생성자를 호출하기 위해 if문이 필요했다.

 

근본적인 문제는 주기 타입 별 다른 검증 정책이다. 따라서 검증 정책을 추상화하여 PeriodValidation으로 분리해 주었다.

만약, 타입이 추가되더라고 해당 클래스에는 변경할 부분이 존재하지 않는다.

각 클래스들은 자신에 맞는 데이터 정책 검증하는 역할만 가지게 된다.

 

그리고 Provider 클래스에선 isSupport 메서드와 filter를 이용해 구체 클래스를 동적으로 선택한다. 이렇게 하면 코드가 매우 간단해진다.

(참고로 Map를 사용하면 isSupport 메서드도 없앨 수 있다.)

 

PeriodValidation 인터페이스를 RepeatPeriod 생성 간, 파라미터로 함께 전달한다.

그러면 기존에 각 주기 타입별 존재하던 생성자를 하나로 통일할 수 있고, if문/중복 코드도 모두 없앨 수 있다.

기존 Sevice에서는 빈으로 등록한 Provider를 주입받고, 필요한 Validator를 꺼내서 사용하면 된다.

 

결과적으로 기존 분기 처리 검증 코드를 아래와 같이 설계를 변경한 것이다.

현재는 간단한 밸리데이션 정책 상황이지만, 만약 복잡한 벨리데이션 정책 환경이라면 전략 패턴에 전략 패턴을 사용해서 책임 및 의존성을 더 줄일 수 있을 것으로 예상된다.

 


마무리

전략 패턴을 학습하고 프로젝트 리팩토링 중 적합한 상황이라 판단해 적용해 보았다.

추상화를 통해 if문 제거, OCP, 의존성 감소, 캡슐화 등의 이점을 얻을 수 있었다.

하지만, 타입 별 관리할 클래스가 늘어나고 그만큼 시스템 복잡도가 증가할 수 있다.

따라서 초기부터 추상화를 고려한 설계보다는 구현 중 추상화가 가능한 부분이 있을 때 진행하는 것이 좋아 보인다.