enum 의 활용(2) - method 추가

2022. 9. 22. 22:41Dev/Java

이전 글에서 간단한 조건문을 enum으로 확장하는 방법에 대해서 알아보았다. 각 value 가 enum의 상수에 강력하게 묶여 있다면 enum에 종속적으로 만들어서 코드를 깔끔하게 정리할 수 있었다. 이번에는 enum 상수에 method들을 묶어 보자.

 

아래와 같은 코드가 있다고 가정해보고 enum으로 refactoring 을 해보자. 

// DayOfWeek 는 앞선 글에서 정의한 요일 상수를 가진 enum 이다.
public void workSchedule(DayOfWeek dayOfWeek) {
    switch (dayOfWeek) {
        case MONDAY:
        case WEDNESDAY:
        case FRIDAY:
            this.workAtOffice();
            break;
        case TUESDAY:
        case THURSDAY:
            this.workAtHome();
            break;
        default:
            this.rest();
    }

}

public void workAtOffice() {
    // 사무실에서 하는 업무 루틴
    System.out.println("work at office");
}

public void workAtHome() {
    // 재택으로 하는 업무 루틴
    System.out.println("work at home");
}

public void rest() {
    // 휴일
    System.out.println("rest");
}

월화수는 사무실로 출근을 하고, 화목은 재택, 토일은 휴식이다.

DayOfWeek 의 상수에 따라 하고자 하는 행위가 결정이 되고 있다. 만약 swtich 문을 잘못 작성한다면 또는 enum 상수의 변경이 있었으나 swtich 문을 업데이트 해주지 않는다면 강력하게 종속되어 있는 상수:행위의 커플링이 불안정해진다. (주석으로 해결하면 되지 않냐고? 주석은 답이 아니다. 주석을 믿지 말고 코드를 믿도록 코드를 짜야 한다. 주석이 업데이트 되지 않아 당신을 속이고 있을 수도 있다.)

 

enum 상수에 묶여 있으니 enum 으로 refactoring 해보자

 

abstract method

enum 상수에 value 를 추가할때 우리는 instant fields 를 추가 했었다. 행위를 추가 하려면 abstract method 를 선언하고 이 선언한 추상 메서드가 enum의 body 에서 구현되도록 코드를 짜주면 된다. 위의 예시를 아래와 같이 바꿔보았다.

public enum DayOfWeek {
    MONDAY("빵","도시락", "샐러드") {
    	// 선언된 추상메서드의 구현부
        @Override
        void work() {
            // 사무실에서 하는 업무 루틴
            System.out.println("work at office");
        }
    },
    TUESDAY("빵","고구마", "고기") {
        // 선언된 추상메서드의 구현부
        @Override
        void work() {
            // 재택으로 하는 업무 루틴
            System.out.println("work at home");
        }
    },
    WEDNESDAY("미역국", "도시락", "샐러드") {
        // 선언된 추상메서드의 구현부
        @Override
        void work() {
            // 사무실에서 하는 업무 루틴
            System.out.println("work at office");
        }
    },
    THURSDAY("샐러드", "햄버거", "카레") {
        // 선언된 추상메서드의 구현부
        @Override
        void work() {
            // 재택으로 하는 업무 루틴
            System.out.println("work at home");
        }
    },
    FRIDAY("카레","샐러드", "고기") {
        // 선언된 추상메서드의 구현부
        @Override
        void work() {
            // 사무실에서 하는 업무 루틴
            System.out.println("work at office");
        }
    },
    SATURDAY(null, "햄버거", "피자") {
        // 선언된 추상메서드의 구현부
        @Override
        void work() {
            // 휴일
            System.out.println("rest");
        }
    },
    SUNDAY(null, "샐러드", "치킨") {
        // 선언된 추상메서드의 구현부
        @Override
        void work() {
            // 휴일
            System.out.println("rest");
        }
    };
    
    // instant fields
    private final String morningMenu;
    private final String lunchMenu;
    private final String dinnerMenu;
    
    // constructor
    DayOfWeek(String morningMenu, String lunchMenu, String dinnerMenu) {
        this.morningMenu = morningMenu;
        this.lunchMenu = lunchMenu;
        this.dinnerMenu = dinnerMenu;
    }
    
    // 추상메서드 선언. 핵심!!
    abstract void work();
    
    
    // getter methods below...
}

우리가 하고자 하는 행위를 enum class 의 본체에는 추상메서드로 선언을 하고 상수의 body 에 상세구현을 해주는 것이 핵심이다. 위 코드의 반복된 구현부를 역시 같이 refactoring 하면 최종적으로 아래와 같아 진다.

 

public enum DayOfWeek {
    MONDAY("빵","도시락", "샐러드") {
        @Override
        void work() {
            new WorkSchedule().workAtOffice();
        }
    },
    TUESDAY("빵","고구마", "고기") {
        @Override
        void work() {
            new WorkSchedule().workAtHome();
        }
    },
    WEDNESDAY("미역국", "도시락", "샐러드") {
        @Override
        void work() {
            new WorkSchedule().workAtOffice();
        }
    },
    THURSDAY("샐러드", "햄버거", "카레") {
        @Override
        void work() {
            new WorkSchedule().workAtHome();
        }
    },
    FRIDAY("카레","샐러드", "고기") {
        @Override
        void work() {
            new WorkSchedule().workAtOffice();
        }
    },
    SATURDAY(null, "햄버거", "피자") {
        @Override
        void work() {
            new WorkSchedule().rest();
        }
    },
    SUNDAY(null, "샐러드", "치킨") {
        @Override
        void work() {
            new WorkSchedule().rest();
        }
    };
    
    private final String morningMenu;
    private final String lunchMenu;
    private final String dinnerMenu;
    
    DayOfWeek(String morningMenu, String lunchMenu, String dinnerMenu) {
        this.morningMenu = morningMenu;
        this.lunchMenu = lunchMenu;
        this.dinnerMenu = dinnerMenu;
    }
    
    abstract void work();
    
    // getter methods below...
}


public class WorkSchedule {
    
    public void workAtOffice() {
        // 사무실에서 하는 업무 루틴
        System.out.println("work at office");
    }
    
    public void workAtHome() {
        // 재택으로 하는 업무 루틴
        System.out.println("work at home");
    }
    
    public void rest() {
        // 휴일
        System.out.println("rest");
    }
}

이렇게 enum에 종속적인 메소드를 refactoring 하면 얻게 되는 이점이 어떤게 있을까.

  1. enum 상수의 유지보수가 쉬워진다. 상수의 추가/변경/삭제시에 행위에 대해서도 같이 구현 해줘야 하므로 위험이 현저히 낮아진다. (추상메서드이므로 반드시 상수에 구현해주어야 한다)
  2. 불필요한 조건문과 불필요한 함수의 작성을 피할 수 있다.
  3. enum 을 사용하는 코드에서는 어떤 상수인지에 대해서 알 필요 없이 행위를 실행시킬 수 있다. 아래의 코드에서 someMethod 를 사용 하는 측에서 상수를 결정해서 넘겨줄 것이므로 someMethod 는 인자로 받는 dayOfWeek 가 'MONDAY' 인지 'TUESDAY' 인지 알 필요가 없다. (의미 없는 깊은 관심은 리소스 낭비일 뿐이다. 관심을 최소화하자.)
public static void someMethod(DayOfWeek dayOfWeek) {
    // do something ...

    // 어떤 enum 상수인지 someMethod 는 알 필요 없다.
    // 상수에서 구현한 work() 를 실행할 뿐이다.
    dayOfWeek.work();

    // do something ...
}

 

interface

보통은 위의 추상메서드를 이용하는데 interface 를 이용해서 역시 동일하게 작성이 가능하다. enum 에 선언한 abstract method 대신에 interface 에 method 를 선언하고 enum 에서 implement 하면 된다.

 

public interface CommonWork {
    void work();
}

public enum DayOfWeekUsingInterface implements CommonWork {
    MONDAY("빵","도시락", "샐러드") {
        @Override
        public void work() {
            new WorkSchedule().workAtOffice();
        }
    },
    TUESDAY("빵","고구마", "고기") {
        @Override
        public void work() {
            new WorkSchedule().workAtHome();
        }
    },
    WEDNESDAY("미역국", "도시락", "샐러드") {
        @Override
        public void work() {
            new WorkSchedule().workAtOffice();
        }
    },
    THURSDAY("샐러드", "햄버거", "카레") {
        @Override
        public void work() {
            new WorkSchedule().workAtHome();
        }
    },
    FRIDAY("카레","샐러드", "고기") {
        @Override
        public void work() {
            new WorkSchedule().workAtOffice();
        }
    },
    SATURDAY(null, "햄버거", "피자") {
        @Override
        public void work() {
            new WorkSchedule().rest();
        }
    },
    SUNDAY(null, "샐러드", "치킨") {
        @Override
        public void work() {
            new WorkSchedule().rest();
        }
    };
    
    private final String morningMenu;
    private final String lunchMenu;
    private final String dinnerMenu;
    
    DayOfWeekUsingInterface(String morningMenu, String lunchMenu, String dinnerMenu) {
        this.morningMenu = morningMenu;
        this.lunchMenu = lunchMenu;
        this.dinnerMenu = dinnerMenu;
    }
    
    // 추상메서드는 없다
    
    // getter methods below...
    
}

 

interface 는 기본적으로 선언을 하고 이를 구현체에서 구현하도록 강제하는 역할을 한다. enum 또한 구현해서 사용이 가능하니 쓸 수 있는 방법이다. 개인적으로는 일반적인 경우 추상메서드를 이용하는 방법을 사용하고 enum 이 비슷한 목적으로 분류해서 사용할때 interface를 사용했다.

  • 여러 외부회사의 API의 응답을 enum 으로 정의할때 각 회사마다 enum 과 상수를 만들고 interface 로 응답에 대한 행위를 구현해서 사용했었다.