이전 시간에는 서비스 로직에서 하드코딩된 부분을 Enum 클래스로 대체하여
코드 로직을 통해 도메인을 파악할 수 있었고, 중복된 코드를 제거할 수 있었습니다.
하지만 Enum 클래스의 공통화까진 진행하지 못하여, 각 Enum 클래스에서 각각의 메소드를 선언해줘야 했습니다.
이번 시간에는 인터페이스를 사용하여 Enum 클래스를 공통화 해볼까요?
그러기 위해서, 알아두면 좋은 Java 개념을 한 번 확인해보고 가봅시다.
1. 타입 파라미터
2. abstact class Enum
- 타입 파라미터는 <T> 형태로 클래스, 인터페이스 또는 메소드 내에서 사용할 수 있고, 특정 유형의 객체를 다룰 수 있게 해줍니다.
- 또한 자바의 모든 Enum 타입은 내부적으로 위의 추상 클래스 java.lang.Enum 을 상속받고 있습니다.
https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html
따라서, 타입 파라미터에 추상클래스 Enum을 명시하므로써, enum타입을 공통화 해줄 수 있습니다.
public interface EnumUtils {
// 타입 파라미터에 Enum과 EnumUtils를 명시하여 파라미터 오브젝트가 Enum의 자식 타입이며, getCode()메소드를 갖고 있다는 것을 보장함!
static <E extends Enum<E> & EnumUtils> boolean hasCode(List<E> codeList, E code) {
return codeList.stream()
.anyMatch(x -> x.getCode().equals(code.getCode()));
}
static <E extends Enum<E> & EnumUtils> boolean isEqual(E firstCode, E secondCode) {
return firstCode.getCode().equals(secondCode.getCode());
}
String getCode();
}
위의 인터페이스를 작성하여, enum 타입들에 대한 유틸 메소드를 공통화해주었습니다.
메소드의 파라미터에 들어올 수 있는 E타입이
추상클래스 Enum을 상속받는 열거형임과 동시에 EnumUtils을 구현하고 있는 클래스임을 확인하고 있습니다.
그럼 이전 게시글에서 사용했던 메소드를 위의 메소드로 바꾸어볼까요?
import static goal.in.next.demo.constant.CategoryCode.*;
import static goal.in.next.demo.utils.EnumUtils.hasCode;
import static goal.in.next.demo.utils.EnumUtils.isEqual;
@Component
public class Validator {
public void validateCategory(PostForm postForm) {
CategoryCode categoryCode = postForm.categoryCode();
if (hasCode(
List.of(CUSTOMER_INQUIRY, REFUND_INQUIRY, TECH_SUPPORT), categoryCode)) {
throw new IllegalArgumentException("잘못된 카테고리입니다");
}
if(isEqual(PRODUCT_REVIEWS, categoryCode)){
throw new IllegalStateException("이상한 카테고리입니다");
}
}
}
@Getter
@AllArgsConstructor
public enum CategoryCode implements EnumUtils {
//..
}
-> 이제 hasCode()와 isEqual()은 EnumUtils을 implements 설정해준 enum 타입이면 모두가 공통적으로 사용할 수 있는 메소드가 되었습니다!
이제 Validator에서의 Enum은 충분히 클-린한 코드가 된 것 같습니다.
이제 모든 작업이 끝난 것일까요??
@Service
@RequiredArgsConstructor
public class PostService {
private final Validator validator;
private final PostRepository postRepository;
@Transactional
public void insertPost(PostForm postForm) {
validator.validateCategory(postForm);
Post post = postForm.toEntity();
postRepository.save(post);
}
}
위의 서비스레이어를 이용해서 값을 저장해봅시다~
@Test
@Rollback(value = false) //DB 확인을 위한 롤백 설정
public void 포스트_저장_테스트(){
Post post = Post.builder()
.someCode(SomeCode.THING)
.title("테스트 제목")
.content("테스트 내용")
.createdAt(LocalDateTime.now())
.categoryCode(CategoryCode.CUSTOMER_INQUIRY)
.deleteType(DeleteType.N)
.build();
Post savedPost = postRepository.save(post);
assertThat(savedPost.getCategoryCode()).isEqualTo(CategoryCode.CUSTOMER_INQUIRY); //성공
}
테스트도 성공하였습니다.
우리의 Enum 작업은 이대로 끝낼 수 있을까요?
마지막으로 혹시나 하는 마음으로 DB를 확인해봅니다.
이럴수가.. ?
Enum 값으로 코드가 제대로 들어가고 있지 않네요?
걱정마세요~ 이 경우는 간단히 수정해 줄 수 있습니다.
@Entity
@Table(name = "post")
@Getter
@NoArgsConstructor
@IdClass(PostId.class)
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
private Long id;
@Id
@Enumerated(EnumType.STRING) //주의 !!
@Column(name="some_code")
private SomeCode someCode;
@Column(name="title")
private String title;
@Column(name="content")
private String content;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Enumerated(EnumType.STRING) // 주의 !!
@Column(name = "category_code")
private CategoryCode categoryCode;
//..
-> 엔티티 해당 필드에 @Enumerated(EnumType.String)을 설정해주지 않으면, Enum 클래스 필드에 선언한 순서대로 숫자값이 들어간다는 것을 깜빡했습니다.
그렇다면 이제 다시 테스트 코드를 실행해볼까요
-> 두둔??
SomeCode 타입의 경우는 아직도 문자열 설정이 되지 않았고,
문자열 설정이 적용된 CategoryCode인 경우는 코드값이 아니라, 상수명 그대로 되었네요???
이게 어찌된 일일까요 ~
꼬꼬무가 되어서 Enum 시리즈를 끝낼 수가 없네요 ~ ㅠ
이 문제는 다음 게시물에서 알아볼게요 ~!
'백엔드 > Java' 카테고리의 다른 글
천사와 악마 사이 추상클래스... - 인터페이스 및 정적 유틸리티 클래스 비교 - 2 (5) | 2024.02.13 |
---|---|
천사와 악마 사이 추상클래스... - 인터페이스 및 정적 유틸리티 클래스 비교 - 1 (0) | 2024.02.01 |
이넘아 놀자 - 3. 코드값으로 되어있는 Enum의 경우 (0) | 2023.08.28 |
이넘아 놀자 - 2. 일단 만들까 이넘 (0) | 2023.08.28 |
이넘아 놀자 - 1. 기존 하드코딩되어 있던 코드 문제점 분석 (0) | 2023.08.27 |