Welcome! Everything is fine.

[Java/Spring] 인터페이스 vs enum 클래스 vs final 클래스 - 상수 관리하는 법 본문

TIL

[Java/Spring] 인터페이스 vs enum 클래스 vs final 클래스 - 상수 관리하는 법

개발곰발 2025. 3. 19.
728x90

지난 프로젝트에서 다른 팀원 분이 상수를 인터페이스로 관리하는 것을 봤다. final 클래스나 enum 클래스를 사용하는 것이 더 나은 것이 아닐까? 하는 궁금증이 들어 검색도 해보고 튜터님께 찾아가 질문해봤다. 

 

예시 코드는 내가 작성한 에러 메세지 enum 클래스인데, GPT한테 각각의 경우에 맞게 수정을 해달라고 해봤다.

final 클래스로 상수 관리하기

상수를 관리하려고 할 때 사용되는 가장 고전적인 방법이 아닐까 싶다. final 클래스를 만들고 , 상수들을 static final로 선언한다. 이렇게 하면  정적 import를 통해 클래스 이름을 생략해서 사용할 수 있다는 장점이 있다. 하지만 확장성과 유지보수성 측면에서 enum보다 떨어진다. 만약 final 클래스로 상수를 관리한다면 기본 생성자를 private으로 설정해 인스턴스화를 방지하는 것을 잊지 말자.

public final class ErrorOrderMessages {
    public static final String CANNOT_CANCEL_ORDER = "취소할 수 없는 주문입니다.";
    public static final String ONLY_USER_CAN_CANCEL = "본인의 주문만 취소할 수 있습니다.";
    public static final String ORDER_NOT_FOUND = "주문 내역이 존재하지 않습니다.";
    public static final String MINIMUM_ORDER_NOT_MET = "최소 주문 금액 이상 주문해야 합니다.";
    public static final String NOT_BUSINESS_HOURS = "현재 영업 시간이 아닙니다.";

    public static final String NOT_OWNER_ORDER = "본인 가게 주문만 처리할 수 있습니다.";
    public static final String ALREADY_DELIVERING_OR_COMPLETED = "이미 배달이 시작되었거나 완료된 주문입니다.";
    public static final String MUST_BE_ACCEPTED_BEFORE_COOKING = "주문 접수 후에 조리를 시작할 수 있습니다.";
    public static final String MUST_BE_COOKING_BEFORE_DELIVERY = "조리를 시작한 주문만 배달할 수 있습니다.";
    public static final String MUST_BE_DELIVERING_BEFORE_COMPLETE = "배달중인 주문만 배달 완료 처리를 할 수 있습니다.";
    public static final String ALREADY_COMPLETED = "이미 배달이 완료된 주문입니다.";

    private ErrorOrderMessages() {} // 인스턴스화 방지
}

 

튜터님은 상속 방지가 더 중요하다거나 캡슐화 자체가 중요하다면 final 클래스를 추천할 수도 있다고 하셨다.

interface로 상수 관리하기

인터페이스의 역할이 뭘까? 인터페이스는 어떤 클래스를 구현할 때 틀을 제공해주는 역할을 한다. 하지만 아래와 같은 경우는 틀을 제공하는 것이 아니라 이미 정해놓은 상수를 모아두는 역할을 할 뿐이다. 이렇게 인터페이스의 역할을 벗어나면 이를 구현하는 클래스들 사이의 일관성이 떨어지게 된다.

public interface ErrorOrderMessage {
    String CANNOT_CANCEL_ORDER = "취소할 수 없는 주문입니다.";
    String ONLY_USER_CAN_CANCEL = "본인의 주문만 취소할 수 있습니다.";
    String ORDER_NOT_FOUND = "주문 내역이 존재하지 않습니다.";
    String MINIMUM_ORDER_NOT_MET = "최소 주문 금액 이상 주문해야 합니다.";
    String NOT_BUSINESS_HOURS = "현재 영업 시간이 아닙니다.";

    String NOT_OWNER_ORDER = "본인 가게 주문만 처리할 수 있습니다.";
    String ALREADY_DELIVERING_OR_COMPLETED = "이미 배달이 시작되었거나 완료된 주문입니다.";
    String MUST_BE_ACCEPTED_BEFORE_COOKING = "주문 접수 후에 조리를 시작할 수 있습니다.";
    String MUST_BE_COOKING_BEFORE_DELIVERY = "조리를 시작한 주문만 배달할 수 있습니다.";
    String MUST_BE_DELIVERING_BEFORE_COMPLETE = "배달중인 주문만 배달 완료 처리를 할 수 있습니다.";
    String ALREADY_COMPLETED = "이미 배달이 완료된 주문입니다.";
}

 

하지만 튜터님 중 한 분은 인터페이스를 선호하진 않지만, 상수만을 저장하는 용도라고 한다면 인터페이스도 많이 사용할 것 같다고 말씀해주셨다. 혹은 상수 재사용이 제일 중요하다 하면 인터페이스를 주로 쓸 수 있을 것이라고 하셨다. 

enum 클래스로 상수 관리하기

만약 에러 메세지를 여러 언어로 제공해야 하는 경우가 생긴다면 어떨까? enum 클래스를 사용한다면 그냥 필드 하나만 추가해주면 돼서 유지보수가 더 쉽다. enum은 특히 잘못된 값을 방지할 수 있어 타입 안정성을 보장한다는 장점이 있다. 

@Getter
public enum ErrorOrderMessage {
    CANNOT_CANCEL_ORDER("취소할 수 없는 주문입니다."),
    ONLY_USER_CAN_CANCEL("본인의 주문만 취소할 수 있습니다."),
    ORDER_NOT_FOUND("주문 내역이 존재하지 않습니다."),
    MINIMUM_ORDER_NOT_MET("최소 주문 금액 이상 주문해야 합니다."),
    NOT_BUSINESS_HOURS("현재 영업 시간이 아닙니다."),

    NOT_OWNER_ORDER("본인 가게 주문만 처리할 수 있습니다."),
    ALREADY_DELIVERING_OR_COMPLETED("이미 배달이 시작되었거나 완료된 주문입니다."),
    MUST_BE_ACCEPTED_BEFORE_COOKING("주문 접수 후에 조리를 시작할 수 있습니다."),
    MUST_BE_COOKING_BEFORE_DELIVERY("조리를 시작한 주문만 배달할 수 있습니다."),
    MUST_BE_DELIVERING_BEFORE_COMPLETE("배달중인 주문만 배달 완료 처리를 할 수 있습니다."),
    ALREADY_COMPLETED("이미 배달이 완료된 주문입니다.");

    private final String message;

    ErrorOrderMessage(String message) {
        this.message = message;
    }
}
  • 타입 안정성 향상 : 사전에 정의된 상수들로만 구성되므로 유효한 값만 입력된다. 유효하지 않은 값이 들어오면 컴파일 오류가 발생한다.
  • 간결성 및 일관성 : 코드가 더 간결해지고 명확해지며, 데이터 일관성이 보장된다.
  • 확장성 : enum에 새로운 상수를 추가하기만 하면 간편하게 확장이 가능하다.

결론

검색해서도 혹은 튜터님께 질문을 해서도 조금씩 의견이 다른 부분이 있었지만, 대부분 타입 안정성과 확장성 측면에서 enum을 주로 선호하는 것 같았다.

 

결론 : 그냥 enum 쓰자.