Programming/Java

[Effective Java] item23 - 태그 달린 클래스보다는 클래스 계층구조를 활용하라!

VSFe 2021. 3. 16. 00:52
태그 달린 클래스보다는 클래스 계층구조를 활용하라!

사실 이 부분은 별로 와닿지 않는 부분이긴 하다. '당연한거 아닌가?' 싶긴 한데... 그래도 일단 정리해보았다.

클래스에 두가지 의미가 있는 요상한 경우가 있다고 해보자. 이번엔 영화관 예시를 들어보자. 이 영화관 멤버십 제도는 일반/VIP로 나뉜다고 하자. VIP를 유지하기 위해선 1년간의 영화 관람 기록을 기반으로 마일리지를 계산해서 판단하지만, 일반이 VIP로 올라가기 위해 1년간 영화를 꾸준히 보는건 어려우니 한달간 관람한 영화를 기준으로 마일리지를 계산해서, 조건이 되면 3개월 후 올려준다고 하자.

이걸 하나의 클래스로 묶어버리면, 다음과 같이 될 것이다.

public class MemberShip {
    enum MemberClass { NORMAL, VIP };

    final MemberClass memberClass;

    private int weeklyMovieCnt;
    private int monthlyMovieCnt;
    private int yearlyMovieCnt;
    private String nickName;

    MemberShip(int weeklyMovieCnt, int monthlyMovieCnt) {
        this.memberClass = MemberClass.NORMAL;
        this.weeklyMovieCnt = weeklyMovieCnt;
        this.monthlyMovieCnt = monthlyMovieCnt;
    }

    MemberShip(int monthlyMovieCnt, int yearlyMovieCnt, String nickName) {
        this.memberClass = MemberClass.VIP;
        this.monthlyMovieCnt = monthlyMovieCnt;
        this.yearlyMovieCnt = yearlyMovieCnt;
        this.nickName = nickName;
    }

    public int get_mile() {
        switch (memberClass) {
            case NORMAL:
                return weeklyMovieCnt * 10 + monthlyMovieCnt * 5;
            case VIP:
                return yearlyMovieCnt * 5 + monthlyMovieCnt * 10;
            default:
                throw new AssertionError(memberClass);
        }
    }
}

딱 보면 알겠지만, 확장성도 떨어지고 코드도 장황해지고 읽기도 힘들며 유지보수도 힘들다...

수많은 문제점이 있지만, 몇개만 강조해서 짚어보자.

  • final로 선언해야 할 땐 모든 변수를 초기화해야 하는데, 일반 유저 인스턴스를 만들고 싶으면 yearlyMovieCnt나 nickName 같은 필요하지도 않은 변수도 초기화 해야 한다.
  • 멤버십 단계를 하나 더 만들고 싶어서 코드를 확장하게 된다면, 사실상 모든 코드를 다 갈아 엎어야 한다.
  • switch문을 사용하는데, 만약 실수로 하나라도 빼먹으면 런타임에러가 발생하고, 그건 컴파일러가 잡아주지 못한다!

사실 이 책을 읽을 수준이 되는 분이라면 "그냥 상위 클래스나 인터페이스 하나 만들고, 상속 구조로 만들면 안되나?" 라는 생각이 들텐데, 사실 그렇게 하면 되는게 맞다.

많은 장점이 소개되어 있지만, 솔직히 말해서 너무 당연한 이야기라 정리를 할 필요를 못 느꼈다.

다만, 클래스를 설계하는 시점에선 태그가 달린 클래스를 실수로 설계할 수 있을텐데, 이런 경우엔 주저하지 않고 계층구조로 리팩토링 할 수 있도록 하자!